Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31f2629ddf | ||
|
|
7b5cc192d9 | ||
|
|
fe9c7e612f | ||
|
|
6c857005c0 | ||
|
|
be020e332b | ||
|
|
6c1d66d8de |
@@ -8,17 +8,22 @@ SOURCE_DIR="$ROOT_DIR/sdlc"
|
|||||||
DELIVERY_ROOT="$ROOT_DIR/sdlc-delivery"
|
DELIVERY_ROOT="$ROOT_DIR/sdlc-delivery"
|
||||||
STAGING_DIR="$ROOT_DIR/.sdlc-delivery.staging"
|
STAGING_DIR="$ROOT_DIR/.sdlc-delivery.staging"
|
||||||
|
|
||||||
# sdlc/ is generated output: the seed rebuilds every work product from the
|
|
||||||
# controlled project data in scripts/sdlc-seed/ before anything is rendered.
|
|
||||||
echo "Seeding work products from project data..."
|
|
||||||
node "$SCRIPT_DIR/sdlc-seed/build.mjs"
|
|
||||||
|
|
||||||
if [[ ! -d "$TOOL_DIR/node_modules/playwright" ]]; then
|
if [[ ! -d "$TOOL_DIR/node_modules/playwright" ]]; then
|
||||||
echo "Installing the local PDF renderer..."
|
echo "Installing the local PDF renderer..."
|
||||||
npm install --prefix "$TOOL_DIR"
|
npm install --prefix "$TOOL_DIR"
|
||||||
npx --prefix "$TOOL_DIR" playwright install chromium
|
npx --prefix "$TOOL_DIR" playwright install chromium
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# The Software Design figures are drawn in scripts/sdlc-seed/figures.mjs and
|
||||||
|
# rasterised with the document font before the seed copies them into sdlc/.
|
||||||
|
echo "Rendering Software Design figures..."
|
||||||
|
node "$TOOL_DIR/render-figures.mjs"
|
||||||
|
|
||||||
|
# sdlc/ is generated output: the seed rebuilds every work product from the
|
||||||
|
# controlled project data in scripts/sdlc-seed/ before anything is rendered.
|
||||||
|
echo "Seeding work products from project data..."
|
||||||
|
node "$SCRIPT_DIR/sdlc-seed/build.mjs"
|
||||||
|
|
||||||
rm -rf "$STAGING_DIR"
|
rm -rf "$STAGING_DIR"
|
||||||
mkdir -p "$STAGING_DIR"
|
mkdir -p "$STAGING_DIR"
|
||||||
|
|
||||||
@@ -29,6 +34,34 @@ while IFS= read -r -d '' source; do
|
|||||||
node "$TOOL_DIR/render-sdlc.mjs" "$source" "$destination"
|
node "$TOOL_DIR/render-sdlc.mjs" "$source" "$destination"
|
||||||
done < <(find "$SOURCE_DIR" -type f -name '*.md' -print0 | sort -z)
|
done < <(find "$SOURCE_DIR" -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..."
|
echo "Verifying staged delivery package..."
|
||||||
"$SCRIPT_DIR/verify-sdlc-delivery.sh" "$STAGING_DIR"
|
"$SCRIPT_DIR/verify-sdlc-delivery.sh" "$STAGING_DIR"
|
||||||
|
|
||||||
|
|||||||
@@ -144,3 +144,8 @@ table.signature-table th:nth-child(4), table.signature-table td:nth-child(4) { w
|
|||||||
reference package, so they take a smaller face to fit the text block. */
|
reference package, so they take a smaller face to fit the text block. */
|
||||||
table.dense { font-size: 7.2pt; }
|
table.dense { font-size: 7.2pt; }
|
||||||
table.dense th, table.dense td { padding: 1mm 1.2mm; }
|
table.dense th, table.dense td { padding: 1mm 1.2mm; }
|
||||||
|
|
||||||
|
/* Figures in the Software Design: full column width, caption under the image. */
|
||||||
|
.delivery-document figure.figure { margin: 8pt 0 12pt; text-align: center; break-inside: avoid; page-break-inside: avoid; }
|
||||||
|
.delivery-document figure.figure img { max-width: 100%; max-height: 225mm; border: .5pt solid #bfbfbf; }
|
||||||
|
.delivery-document figure.figure figcaption { margin-top: 4pt; font-size: 9.5pt; font-weight: 700; }
|
||||||
|
|||||||
@@ -188,15 +188,19 @@ LOGO_MIN_PX = 100
|
|||||||
def strip_logo_images(document):
|
def strip_logo_images(document):
|
||||||
"""Drop the letterhead logo left loose in the body.
|
"""Drop the letterhead logo left loose in the body.
|
||||||
|
|
||||||
The work products carry no figures of their own, but the converter draws
|
The converter draws each bullet as a small image, so only the logo is
|
||||||
each bullet as a small image, so only the logo is removed - anything that
|
removed: it is square and at least LOGO_MIN_PX, whereas anything smaller is
|
||||||
size would be furniture, anything smaller is a list marker.
|
a list marker. The Software Design figures are wider than they are tall,
|
||||||
|
so they are kept.
|
||||||
"""
|
"""
|
||||||
|
def is_logo(image):
|
||||||
|
width, height = image.px_width, image.px_height
|
||||||
|
return max(width, height) >= LOGO_MIN_PX and 0.8 <= width / max(height, 1) <= 1.25
|
||||||
|
|
||||||
logos = {
|
logos = {
|
||||||
relationship_id
|
relationship_id
|
||||||
for relationship_id, part in document.part.related_parts.items()
|
for relationship_id, part in document.part.related_parts.items()
|
||||||
if getattr(part, 'image', None) is not None
|
if getattr(part, 'image', None) is not None and is_logo(part.image)
|
||||||
and max(part.image.px_width, part.image.px_height) >= LOGO_MIN_PX
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removed = 0
|
removed = 0
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Rasterise the Software Design figures (scripts/sdlc-seed/figures.mjs) to PNG with the
|
||||||
|
// document font, so Thai labels print the same in the PDF and survive the Word conversion.
|
||||||
|
// Usage: node scripts/sdlc-delivery/render-figures.mjs
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { chromium } from 'playwright';
|
||||||
|
import { FIGURES } from '../sdlc-seed/figures.mjs';
|
||||||
|
|
||||||
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const output = path.resolve(here, '../sdlc-seed/figures');
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const font = await fs.readFile(path.join(here, 'assets/LeelawadeeUI.ttf'));
|
||||||
|
const fontFace = `@font-face { font-family: "BRN Thai"; src: url(data:font/ttf;base64,${font.toString('base64')}) format("truetype"); font-weight: 400 700; }`;
|
||||||
|
await fs.mkdir(output, { recursive: true });
|
||||||
|
const browser = await chromium.launch({ headless: true });
|
||||||
|
try {
|
||||||
|
const page = await (await browser.newContext({ deviceScaleFactor: 2 })).newPage();
|
||||||
|
for (const [file, , draw] of Object.values(FIGURES)) {
|
||||||
|
const svg = draw();
|
||||||
|
const [, w, h] = svg.match(/width="(\d+)" height="(\d+)"/);
|
||||||
|
await page.setViewportSize({ width: Number(w), height: Number(h) });
|
||||||
|
await page.setContent(`<!doctype html><html><head><meta charset="utf-8"><style>${fontFace} html,body{margin:0}</style></head><body>${svg}</body></html>`);
|
||||||
|
await page.evaluate(() => document.fonts.ready);
|
||||||
|
await page.locator('svg').first().screenshot({ path: path.join(output, `${file}.png`) });
|
||||||
|
console.log(`Rendered figures/${file}.png`);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => { console.error(error); process.exit(1); });
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { chromium } from 'playwright';
|
import { chromium } from 'playwright';
|
||||||
@@ -101,7 +102,7 @@ function renderParagraph(lines) {
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function markdownToHtml(markdown) {
|
function markdownToHtml(markdown, baseDir) {
|
||||||
const lines = markdown.replaceAll('\r\n', '\n').split('\n');
|
const lines = markdown.replaceAll('\r\n', '\n').split('\n');
|
||||||
const output = [];
|
const output = [];
|
||||||
let index = 0;
|
let index = 0;
|
||||||
@@ -110,6 +111,15 @@ function markdownToHtml(markdown) {
|
|||||||
const line = lines[index];
|
const line = lines[index];
|
||||||
if (!line.trim()) { index += 1; continue; }
|
if (!line.trim()) { index += 1; continue; }
|
||||||
|
|
||||||
|
// A figure: , embedded so the PDF needs no external files.
|
||||||
|
const image = line.match(/^!\[([^\]]*)\]\(([^)]+)\)\s*$/);
|
||||||
|
if (image) {
|
||||||
|
const data = readFileSync(path.resolve(baseDir, image[2])).toString('base64');
|
||||||
|
output.push(`<figure class="figure"><img src="data:image/png;base64,${data}" alt=""><figcaption>${renderInline(image[1])}</figcaption></figure>`);
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (isTableLine(line)) {
|
if (isTableLine(line)) {
|
||||||
const table = [];
|
const table = [];
|
||||||
while (index < lines.length && isTableLine(lines[index])) table.push(lines[index++]);
|
while (index < lines.length && isTableLine(lines[index])) table.push(lines[index++]);
|
||||||
@@ -149,7 +159,7 @@ function markdownToHtml(markdown) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const paragraph = [];
|
const paragraph = [];
|
||||||
while (index < lines.length && lines[index].trim() && !isTableLine(lines[index]) && !/^(#{1,3})\s+/.test(lines[index]) && !/^[-*]\s+/.test(lines[index]) && !/^\d+\.\s+/.test(lines[index])) paragraph.push(lines[index++]);
|
while (index < lines.length && lines[index].trim() && !isTableLine(lines[index]) && !/^!\[/.test(lines[index]) && !/^(#{1,3})\s+/.test(lines[index]) && !/^[-*]\s+/.test(lines[index]) && !/^\d+\.\s+/.test(lines[index])) paragraph.push(lines[index++]);
|
||||||
const isApproval = paragraph.some((line) => /^(Name|Role|Signature|Date|Position|Company|Project roles|Decision):/.test(line.trimEnd()));
|
const isApproval = paragraph.some((line) => /^(Name|Role|Signature|Date|Position|Company|Project roles|Decision):/.test(line.trimEnd()));
|
||||||
const html = `<p${isApproval ? ' class="approval-fields"' : ''}>${renderParagraph(paragraph)}</p>`;
|
const html = `<p${isApproval ? ' class="approval-fields"' : ''}>${renderParagraph(paragraph)}</p>`;
|
||||||
// Keep a signatory's heading and fields on one page.
|
// Keep a signatory's heading and fields on one page.
|
||||||
@@ -226,7 +236,7 @@ async function main() {
|
|||||||
? '@page { size: A4 landscape; margin: 32mm 15mm 24mm 15mm; }'
|
? '@page { size: A4 landscape; margin: 32mm 15mm 24mm 15mm; }'
|
||||||
: '';
|
: '';
|
||||||
const templateFont = `<style>${fontFace}</style>`;
|
const templateFont = `<style>${fontFace}</style>`;
|
||||||
const html = `<!doctype html><html><head><meta charset="utf-8"><style>${fontFace}${css}${pageLayout}</style></head><body><main class="delivery-document">${markdownToHtml(body)}</main></body></html>`;
|
const html = `<!doctype html><html><head><meta charset="utf-8"><style>${fontFace}${css}${pageLayout}</style></head><body><main class="delivery-document">${markdownToHtml(body, path.dirname(source))}</main></body></html>`;
|
||||||
|
|
||||||
await fs.mkdir(path.dirname(destination), { recursive: true });
|
await fs.mkdir(path.dirname(destination), { recursive: true });
|
||||||
const browser = await chromium.launch({ headless: true });
|
const browser = await chromium.launch({ headless: true });
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ function extractDocumentContent(markdown) {
|
|||||||
.slice((titleMatch?.index ?? 0) + (titleMatch?.[0].length ?? 0))
|
.slice((titleMatch?.index ?? 0) + (titleMatch?.[0].length ?? 0))
|
||||||
.replace(/^<!--\s*footer:.*?-->\s*$/m, '')
|
.replace(/^<!--\s*footer:.*?-->\s*$/m, '')
|
||||||
.replace(/<br\s*\/?>/gi, ' ') // markup, not document text
|
.replace(/<br\s*\/?>/gi, ' ') // markup, not document text
|
||||||
|
// A figure prints its caption; the image path is markup.
|
||||||
|
.replace(/^!\[([^\]]*)\]\([^)]*\)\s*$/gm, '$1')
|
||||||
// List markers are drawn by the renderer, not stored as text: the browser
|
// List markers are drawn by the renderer, not stored as text: the browser
|
||||||
// paints them from <ol>/<ul> and Word from numbering.xml, so they are
|
// paints them from <ol>/<ul> and Word from numbering.xml, so they are
|
||||||
// markup here too and must not be expected in the extracted text.
|
// markup here too and must not be expected in the extracted text.
|
||||||
|
|||||||
@@ -16,16 +16,43 @@ Approval signature tables on every document.
|
|||||||
| `history.mjs` | Work Schedule, reporting periods, meetings, Correction Register, Change Reports |
|
| `history.mjs` | Work Schedule, reporting periods, meetings, Correction Register, Change Reports |
|
||||||
| `lib.mjs` | Buddhist-calendar dates, Markdown tables, signature blocks |
|
| `lib.mjs` | Buddhist-calendar dates, Markdown tables, signature blocks |
|
||||||
| `format.mjs` | The shared work-product scaffold |
|
| `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 |
|
| `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 |
|
| `build.mjs` | Writes all documents and checks no folder is left empty |
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node scripts/sdlc-seed/build.mjs # regenerate sdlc/ only
|
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
|
The traceability chain is validated at import time: every Customer Requirement
|
||||||
must resolve to existing Software Requirements, Software Units, and a Test Case,
|
must resolve to existing Software Requirements, Software Units, and a Test Case,
|
||||||
or the build fails.
|
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.
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ import * as pm2 from './docs-pm2.mjs';
|
|||||||
import * as si from './docs-si.mjs';
|
import * as si from './docs-si.mjs';
|
||||||
import * as si2 from './docs-si2.mjs';
|
import * as si2 from './docs-si2.mjs';
|
||||||
import * as other from './docs-other.mjs';
|
import * as other from './docs-other.mjs';
|
||||||
|
import { hindsightErrors } from './timeline.mjs';
|
||||||
|
|
||||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
||||||
const target = path.join(root, 'sdlc');
|
const target = path.join(root, 'sdlc');
|
||||||
|
const figureSource = path.join(path.dirname(fileURLToPath(import.meta.url)), 'figures');
|
||||||
|
|
||||||
// Folders that must exist even when a work product is evidence rather than a document.
|
// Folders that must exist even when a work product is evidence rather than a document.
|
||||||
const FOLDERS = [
|
const FOLDERS = [
|
||||||
@@ -69,9 +71,21 @@ async function main() {
|
|||||||
if (!FOLDERS.includes(d.dir)) throw new Error(`Unknown folder for ${d.name}: ${d.dir}`);
|
if (!FOLDERS.includes(d.dir)) throw new Error(`Unknown folder for ${d.name}: ${d.dir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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- ')}`);
|
||||||
|
|
||||||
await fs.rm(target, { recursive: true, force: true });
|
await fs.rm(target, { recursive: true, force: true });
|
||||||
for (const folder of FOLDERS) await fs.mkdir(path.join(target, folder), { recursive: 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');
|
for (const d of docs) {
|
||||||
|
await fs.writeFile(path.join(target, d.dir, `${d.name}.md`), d.md, 'utf8');
|
||||||
|
// Figures referenced as  are copied next to the document.
|
||||||
|
for (const [, file] of d.md.matchAll(/^!\[[^\]]*\]\(figures\/([^)]+)\)$/gm)) {
|
||||||
|
const from = path.join(figureSource, file);
|
||||||
|
try { await fs.access(from); } catch { throw new Error(`${d.name} references missing figure ${file}; run scripts/sdlc-delivery/render-figures.mjs`); }
|
||||||
|
await fs.mkdir(path.join(target, d.dir, 'figures'), { recursive: true });
|
||||||
|
await fs.copyFile(from, path.join(target, d.dir, 'figures', file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const empty = [];
|
const empty = [];
|
||||||
for (const folder of FOLDERS) {
|
for (const folder of FOLDERS) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// 3-Other Document: Stakeholder Register, Project Charter, Traceability Record Table, Training Report, List of Evidence.
|
// 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 { SRS, UNITS, TEST_CASES, traceOf, srTopic, unitTopic } from './engineering.mjs';
|
||||||
import { CORRECTIONS, CHANGES, PERIODS, MEETINGS, TASKS } from './history.mjs';
|
import { CORRECTIONS, PERIODS, MEETINGS, TASKS } from './history.mjs';
|
||||||
import { bud, thLong, thRangeLong, table, bullets, numbered, daysBetween } from './lib.mjs';
|
import { bud, thLong, thRangeLong, table, bullets, numbered, daysBetween } from './lib.mjs';
|
||||||
import { workProduct, head } from './format.mjs';
|
import { workProduct, head } from './format.mjs';
|
||||||
|
|
||||||
@@ -104,8 +104,8 @@ export function projectCharter() {
|
|||||||
['อนุมัติกฎบัตรโครงการ', thLong('2026-01-23'), 'Project Sponsor อนุมัติขอบเขตและวัตถุประสงค์'],
|
['อนุมัติกฎบัตรโครงการ', thLong('2026-01-23'), 'Project Sponsor อนุมัติขอบเขตและวัตถุประสงค์'],
|
||||||
['ตั้ง Baseline ความต้องการ', thLong('2026-02-18'), 'อนุมัติ Customer Requirements และแผนงาน'],
|
['ตั้ง Baseline ความต้องการ', thLong('2026-02-18'), 'อนุมัติ Customer Requirements และแผนงาน'],
|
||||||
['Baseline การพัฒนา', thLong('2026-05-29'), 'พัฒนาครบทุกโมดูลตามขอบเขต'],
|
['Baseline การพัฒนา', thLong('2026-05-29'), 'พัฒนาครบทุกโมดูลตามขอบเขต'],
|
||||||
['ทดสอบระบบและ UAT', thRangeLong(TEST_FROM, TEST_TO), 'ผลการทดสอบผ่านครบทุกรายการ'],
|
['ทดสอบระบบและ UAT', thRangeLong(TEST_FROM, TEST_TO), 'Test Case และ UAT ผ่านครบทุกรายการ'],
|
||||||
['ตรวจรับส่งมอบ', thLong(ACCEPT_DATE), 'ผลการตรวจรับ Accepted'],
|
['ตรวจรับส่งมอบ', thLong(ACCEPT_DATE), 'Project Sponsor ลงนามตรวจรับสิ่งส่งมอบ'],
|
||||||
['ปิดโครงการ', thLong(END), 'ปิดโครงการอย่างเป็นทางการ'],
|
['ปิดโครงการ', thLong(END), 'ปิดโครงการอย่างเป็นทางการ'],
|
||||||
], ['', 'c', '']),
|
], ['', 'c', '']),
|
||||||
].join('\n\n'),
|
].join('\n\n'),
|
||||||
@@ -119,8 +119,9 @@ export function traceabilityTable() {
|
|||||||
const date = '2026-08-17';
|
const date = '2026-08-17';
|
||||||
const rows = CRS.map(([id, text]) => {
|
const rows = CRS.map(([id, text]) => {
|
||||||
const t = traceOf(id);
|
const t = traceOf(id);
|
||||||
return [id, text, t.sr.join(', '), t.un.join(', '), t.tc, 'Passed', 'Passed'];
|
return [id, text, t.sr.join(', '), t.un.join(', ') || '-', t.tc, t.ev || '-', 'Passed', 'Passed'];
|
||||||
});
|
});
|
||||||
|
const traced = CRS.map(([id]) => traceOf(id));
|
||||||
return {
|
return {
|
||||||
dir: OTHER,
|
dir: OTHER,
|
||||||
name: `${CODE} TRACEABILITY-RECORD ${bud(date)} V1.0 NoC`,
|
name: `${CODE} TRACEABILITY-RECORD ${bud(date)} V1.0 NoC`,
|
||||||
@@ -136,14 +137,15 @@ export function traceabilityTable() {
|
|||||||
'## วัตถุประสงค์',
|
'## วัตถุประสงค์',
|
||||||
'ตารางนี้สรุปเส้นทางการสอบกลับของความต้องการทุกรายการ ตั้งแต่ความต้องการของลูกค้าจนถึงผลการทดสอบและผลการทดสอบการยอมรับ เพื่อใช้ตรวจสอบความครบถ้วนได้ในหน้าเดียว โดยรายละเอียดหัวข้อของแต่ละรหัสอยู่ในเอกสาร Traceability Record (WP 13)',
|
'ตารางนี้สรุปเส้นทางการสอบกลับของความต้องการทุกรายการ ตั้งแต่ความต้องการของลูกค้าจนถึงผลการทดสอบและผลการทดสอบการยอมรับ เพื่อใช้ตรวจสอบความครบถ้วนได้ในหน้าเดียว โดยรายละเอียดหัวข้อของแต่ละรหัสอยู่ในเอกสาร Traceability Record (WP 13)',
|
||||||
'## ตารางสรุปการสอบกลับ',
|
'## ตารางสรุปการสอบกลับ',
|
||||||
table(['CR ID', 'Customer Requirement', 'SRS ID', 'Unit ID', 'Test Case ID', 'ผลการทดสอบ', 'ผล UAT'], rows, ['', '', '', '', 'c', 'c', 'c']),
|
table(['CR ID', 'Customer Requirement', 'SRS ID', 'Unit ID', 'Test Case ID', 'หลักฐานอื่น (Document)', 'ผลการทดสอบ', 'ผล UAT'], rows, ['', '', '', '', 'c', '', 'c', 'c']),
|
||||||
'## สรุปความครอบคลุม',
|
'## สรุปความครอบคลุม',
|
||||||
table(['รายการ', 'จำนวน'], [
|
table(['รายการ', 'จำนวน'], [
|
||||||
['ความต้องการของลูกค้าทั้งหมด', CRS.length],
|
['ความต้องการของลูกค้าทั้งหมด', CRS.length],
|
||||||
['เชื่อมโยงกับความต้องการซอฟต์แวร์', CRS.length],
|
['เชื่อมโยงกับความต้องการซอฟต์แวร์', CRS.length],
|
||||||
['เชื่อมโยงกับ Software Unit', CRS.length],
|
['เชื่อมโยงกับ Software Unit', traced.filter((t) => t.un.length).length],
|
||||||
['เชื่อมโยงกับ Test Case', CRS.length],
|
['เชื่อมโยงกับ Test Case', traced.filter((t) => t.tcs.length).length],
|
||||||
['ผลการทดสอบผ่าน', CRS.length],
|
['ยืนยันด้วยเอกสารส่งมอบ (ไม่มี Software Unit)', traced.filter((t) => !t.tcs.length).length],
|
||||||
|
['ผลการทดสอบหรือการตรวจสอบเอกสารผ่าน', CRS.length],
|
||||||
['ผลการทดสอบการยอมรับผ่าน', CRS.length],
|
['ผลการทดสอบการยอมรับผ่าน', CRS.length],
|
||||||
], ['', 'r']),
|
], ['', 'r']),
|
||||||
].join('\n\n'),
|
].join('\n\n'),
|
||||||
@@ -246,7 +248,7 @@ export function listOfEvidence(inventory) {
|
|||||||
[14, 'Software Components', one('14.Software Components'), 'เอกสารแสดงส่วนประกอบต่าง ๆ ของโปรแกรมแต่ละเวอร์ชัน'],
|
[14, 'Software Components', one('14.Software Components'), 'เอกสารแสดงส่วนประกอบต่าง ๆ ของโปรแกรมแต่ละเวอร์ชัน'],
|
||||||
[15, 'Test Cases and Test Procedures', one('15.Test Cases'), 'เอกสารแสดงตัวอย่างชุดข้อมูลที่ใช้ทดสอบ'],
|
[15, 'Test Cases and Test Procedures', one('15.Test Cases'), 'เอกสารแสดงตัวอย่างชุดข้อมูลที่ใช้ทดสอบ'],
|
||||||
[16, 'Test Report', one('16.Test Report'), 'บันทึกผลการทดสอบระบบ'],
|
[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'), 'เอกสารคู่มือการใช้งานสำหรับผู้ใช้'],
|
[18, 'Software User Documentation', one('18.Software User Documentation'), 'เอกสารคู่มือการใช้งานสำหรับผู้ใช้'],
|
||||||
[19, 'Product Operation Guide', one('19.Product Operation Guide'), 'เอกสารคู่มือปฏิบัติงานสำหรับผู้ดูแลระบบ'],
|
[19, 'Product Operation Guide', one('19.Product Operation Guide'), 'เอกสารคู่มือปฏิบัติงานสำหรับผู้ดูแลระบบ'],
|
||||||
[20, 'Maintenance Documentation', one('20.Maintenance Documentation'), 'เอกสารคู่มือการบำรุงรักษาระบบ'],
|
[20, 'Maintenance Documentation', one('20.Maintenance Documentation'), 'เอกสารคู่มือการบำรุงรักษาระบบ'],
|
||||||
@@ -270,7 +272,7 @@ export function listOfEvidence(inventory) {
|
|||||||
['เอกสารที่จัดเตรียมครบถ้วน', inventory.length],
|
['เอกสารที่จัดเตรียมครบถ้วน', inventory.length],
|
||||||
['เอกสารที่ยังไม่ได้จัดเตรียม', 0],
|
['เอกสารที่ยังไม่ได้จัดเตรียม', 0],
|
||||||
], ['', 'r']),
|
], ['', 'r']),
|
||||||
`เอกสารและหลักฐานทั้งหมดจัดเก็บใน Project Repository และสำรองไว้ตามที่ระบุในเอกสาร Project Repository (Backup) โดยชุดเอกสารส่งมอบควบคุมด้วย Tag \`${TAG}\``,
|
`เอกสารและหลักฐานทั้งหมดจัดเก็บใน Project Repository และสำรองไว้ตามที่ระบุในเอกสาร Project Repository (Backup) โดยชุดเอกสารส่งมอบควบคุมใน Git Repository`,
|
||||||
].join('\n\n'),
|
].join('\n\n'),
|
||||||
secretary: P.YaB, reviewer: P.ApS, approver: P.SeV,
|
secretary: P.YaB, reviewer: P.ApS, approver: P.SeV,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// PM work products 1–2: Statement of Work, Work Schedule, Software Project Plan, Customer Requirements.
|
// PM work products 1–2: Statement of Work, Work Schedule, Software Project Plan, Customer Requirements.
|
||||||
import { P, TEAM, CODE, PROJECT_TH, COMPANY_TH, ORGANIZER, ORGANIZER_PM, RECORDER, START, END, WPS, SCOPE, CR_GROUPS, CRS, DEV_START, TEST_FROM, TEST_TO } from './project.mjs';
|
import { P, TEAM, CODE, PROJECT_TH, COMPANY_TH, ORGANIZER, ORGANIZER_PM, RECORDER, START, END, WPS, SCOPE, CR_GROUPS, CRS, DEV_START, TEST_FROM, TEST_TO } from './project.mjs';
|
||||||
import { TASKS } from './history.mjs';
|
import { TASKS, VERIFICATION_ROUNDS, VERIFICATION_RULES } from './history.mjs';
|
||||||
import { bud, thLong, thRangeLong, thShort, table, bullets, numbered, daysBetween, doc } from './lib.mjs';
|
import { bud, thLong, thRangeLong, thShort, table, bullets, numbered, daysBetween, doc } from './lib.mjs';
|
||||||
import { workProduct, head } from './format.mjs';
|
import { workProduct, head } from './format.mjs';
|
||||||
|
|
||||||
@@ -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() {
|
export function workSchedule() {
|
||||||
const date = '2026-02-13';
|
const date = '2026-02-13';
|
||||||
return {
|
return {
|
||||||
@@ -88,7 +91,7 @@ export function workSchedule() {
|
|||||||
body: [
|
body: [
|
||||||
'## แผนการดำเนินงาน (Work Schedule)',
|
'## แผนการดำเนินงาน (Work Schedule)',
|
||||||
table(['No.', 'Phase', 'Task', 'รายละเอียด', 'ผู้รับผิดชอบ', 'Start', 'Finish', 'วัน', 'สิ่งส่งมอบ / หลักฐาน', 'Status', 'หมายเหตุ'],
|
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', '']),
|
['c', '', '', '', 'c', 'r', 'r', 'r', '', 'c', '']),
|
||||||
'## หลักไมล์ของโครงการ (Milestones)',
|
'## หลักไมล์ของโครงการ (Milestones)',
|
||||||
table(['หลักไมล์', 'วันที่', 'เกณฑ์การบรรลุ'], [
|
table(['หลักไมล์', 'วันที่', 'เกณฑ์การบรรลุ'], [
|
||||||
@@ -96,10 +99,10 @@ export function workSchedule() {
|
|||||||
['อนุมัติกฎบัตรโครงการ', thLong('2026-01-23'), 'Project Sponsor อนุมัติ Project Charter'],
|
['อนุมัติกฎบัตรโครงการ', thLong('2026-01-23'), 'Project Sponsor อนุมัติ Project Charter'],
|
||||||
['ตั้ง Baseline ความต้องการและแผนงาน', thLong('2026-02-18'), 'อนุมัติ Customer Requirements และ Work Schedule'],
|
['ตั้ง Baseline ความต้องการและแผนงาน', thLong('2026-02-18'), 'อนุมัติ Customer Requirements และ Work Schedule'],
|
||||||
['เริ่มพัฒนาระบบ', thLong(DEV_START), 'เริ่ม Task 3.1 ตามแผน'],
|
['เริ่มพัฒนาระบบ', thLong(DEV_START), 'เริ่ม Task 3.1 ตามแผน'],
|
||||||
['Baseline การพัฒนา', thLong('2026-05-29'), 'พัฒนาครบทุกโมดูล (Git a0677d6)'],
|
['Baseline การพัฒนา', thLong('2026-05-29'), 'พัฒนาครบทุกโมดูลตามขอบเขต และกำหนด Git Baseline การพัฒนา'],
|
||||||
['ทดสอบระบบและ UAT', thRangeLong(TEST_FROM, TEST_TO), 'ทดสอบ 45 Test Case และ 12 สถานการณ์ UAT ผ่านทั้งหมด'],
|
['ทดสอบระบบและ UAT', thRangeLong(TEST_FROM, TEST_TO), `Test Case ผ่านครบ และ UAT ผ่านครบ 14 หมวดความต้องการ (${CRS.length} รายการ)`],
|
||||||
['ตรวจรับส่งมอบระบบ', thLong('2026-08-17'), 'ผลการตรวจรับ Accepted'],
|
['ตรวจรับส่งมอบระบบ', thLong('2026-08-17'), 'Project Sponsor ลงนามตรวจรับสิ่งส่งมอบครบทุกรายการ'],
|
||||||
['อบรมผู้ใช้งาน', thLong('2026-08-22'), 'อบรมผู้ใช้งาน 6 คน'],
|
['อบรมผู้ใช้งาน', thLong('2026-08-22'), 'ผู้ใช้งานทุกฝ่ายที่เกี่ยวข้องผ่านการอบรม'],
|
||||||
['ปิดโครงการ', thLong(END), 'ปิดโครงการอย่างเป็นทางการ'],
|
['ปิดโครงการ', thLong(END), 'ปิดโครงการอย่างเป็นทางการ'],
|
||||||
], ['', 'c', '']),
|
], ['', 'c', '']),
|
||||||
'## หมายเหตุการติดตามแผนงาน',
|
'## หมายเหตุการติดตามแผนงาน',
|
||||||
@@ -165,15 +168,15 @@ export function softwareProjectPlan() {
|
|||||||
'**ด้านคุณภาพของระบบ**',
|
'**ด้านคุณภาพของระบบ**',
|
||||||
table(['ลำดับ', 'หัวข้อ', 'เกณฑ์คุณภาพ', 'วิธีประเมิน'], [
|
table(['ลำดับ', 'หัวข้อ', 'เกณฑ์คุณภาพ', 'วิธีประเมิน'], [
|
||||||
['1.1', 'ความถูกต้องของฟังก์ชัน', 'ฟังก์ชันทั้งหมดทำงานตรงตาม Customer Requirements ครบ 100%', 'ทดสอบตาม Test Case และ UAT'],
|
['1.1', 'ความถูกต้องของฟังก์ชัน', 'ฟังก์ชันทั้งหมดทำงานตรงตาม Customer Requirements ครบ 100%', 'ทดสอบตาม Test Case และ UAT'],
|
||||||
['1.2', 'ความถูกต้องของข้อมูลสต๊อกและบัญชี', 'ไม่พบยอดสต๊อกติดลบหรือรายการ GL ที่ไม่สมดุล', 'ทดสอบ Transaction และ Rollback (TC-UN08.002)'],
|
['1.2', 'ความถูกต้องของข้อมูลสต๊อกและบัญชี', 'ไม่พบยอดสต๊อกติดลบหรือรายการ GL ที่ไม่สมดุล', 'ทดสอบ Transaction และ Rollback ของการบันทึกบัญชี'],
|
||||||
['1.3', 'ความปลอดภัยของระบบ', 'ไม่พบข้อบกพร่องระดับวิกฤตด้านสิทธิ์และการแยกข้อมูล', 'ทดสอบสิทธิ์เชิงลบ (TC-UN12.001, TC-UN12.002)'],
|
['1.3', 'ความปลอดภัยของระบบ', 'ไม่พบข้อบกพร่องระดับวิกฤตด้านสิทธิ์และการแยกข้อมูล', 'ทดสอบสิทธิ์เชิงลบและการแยกข้อมูลระหว่างบริษัท'],
|
||||||
['1.4', 'ประสิทธิภาพการตอบสนอง', 'งานประจำวันตอบสนองภายในเวลาที่ใช้งานได้จริงบนสภาพแวดล้อมที่ตกลง', 'ทดสอบตามปริมาณข้อมูลตัวอย่าง (TC-UN09.001)'],
|
['1.4', 'ประสิทธิภาพการตอบสนอง', 'งานประจำวันตอบสนองภายในเวลาที่ใช้งานได้จริงบนสภาพแวดล้อมที่ตกลง', 'ทดสอบตามปริมาณข้อมูลตัวอย่างบนแดชบอร์ดและรายงาน'],
|
||||||
['1.5', 'ความสามารถในการเข้าถึง', 'ใช้งานได้บน Desktop, Tablet และ Mobile (Responsive)', 'ทดสอบข้ามอุปกรณ์'],
|
['1.5', 'ความสามารถในการเข้าถึง', 'ใช้งานได้บน Desktop, Tablet และ Mobile (Responsive)', 'ทดสอบข้ามอุปกรณ์'],
|
||||||
], ['c']),
|
], ['c']),
|
||||||
'**ด้านการดูแลหลังส่งมอบ**',
|
'**ด้านการดูแลหลังส่งมอบ**',
|
||||||
table(['ลำดับ', 'หัวข้อ', 'เกณฑ์คุณภาพ', 'วิธีประเมิน'], [
|
table(['ลำดับ', 'หัวข้อ', 'เกณฑ์คุณภาพ', 'วิธีประเมิน'], [
|
||||||
['2.1', 'การตอบสนองเหตุขัดข้อง', 'ตอบรับภายใน 4 ชั่วโมง และแก้ไขตามระดับความรุนแรงที่กำหนดใน SLA', 'บันทึกการให้บริการ'],
|
['2.1', 'การตอบสนองเหตุขัดข้อง', 'ตอบรับภายใน 4 ชั่วโมง และแก้ไขตามระดับความรุนแรงที่กำหนดใน SLA', 'บันทึกการให้บริการ'],
|
||||||
['2.2', 'การสำรองข้อมูล', 'มีระบบสำรองข้อมูลอัตโนมัติรายวันและทดสอบกู้คืนได้จริง', 'Backup & Restore Test (TC-UN13.004)'],
|
['2.2', 'การสำรองข้อมูล', 'มีระบบสำรองข้อมูลอัตโนมัติรายวันและทดสอบกู้คืนได้จริง', 'ทดสอบสำรองและกู้คืนข้อมูล (Backup & Restore Test)'],
|
||||||
['2.3', 'การเฝ้าระวังระบบ', 'ตรวจสอบสถานะบริการทุก 5 นาที และแจ้งเตือนเมื่อล้มเหลว 2 ครั้งติดกัน', 'บันทึกการเฝ้าระวัง'],
|
['2.3', 'การเฝ้าระวังระบบ', 'ตรวจสอบสถานะบริการทุก 5 นาที และแจ้งเตือนเมื่อล้มเหลว 2 ครั้งติดกัน', 'บันทึกการเฝ้าระวัง'],
|
||||||
], ['c']),
|
], ['c']),
|
||||||
sec(4, 'Software Development Life Cycle Methodology'),
|
sec(4, 'Software Development Life Cycle Methodology'),
|
||||||
@@ -185,11 +188,11 @@ export function softwareProjectPlan() {
|
|||||||
'ตรวจสอบย้อนกลับ (Audit) และตรวจรับงานได้อย่างเป็นทางการ',
|
'ตรวจสอบย้อนกลับ (Audit) และตรวจรับงานได้อย่างเป็นทางการ',
|
||||||
]),
|
]),
|
||||||
table(['ขั้นตอน', 'แนวทางที่ใช้จริงในโครงการ'], [
|
table(['ขั้นตอน', 'แนวทางที่ใช้จริงในโครงการ'], [
|
||||||
['Requirements', 'เก็บความต้องการและตั้ง Baseline ก่อนเริ่มพัฒนา การเปลี่ยนแปลงภายหลังผ่าน Change Report (CH-001–CH-003)'],
|
['Requirements', 'เก็บความต้องการและตั้ง Baseline ก่อนเริ่มพัฒนา การเปลี่ยนแปลงภายหลังต้องพิจารณาตามเกณฑ์ใน Change Report ก่อนดำเนินการ'],
|
||||||
['Design', 'ออกแบบสถาปัตยกรรม โครงสร้างฐานข้อมูล และ Software Unit ก่อนพัฒนาแต่ละโมดูล'],
|
['Design', 'ออกแบบสถาปัตยกรรม โครงสร้างฐานข้อมูล และ Software Unit ก่อนพัฒนาแต่ละโมดูล'],
|
||||||
['Implementation', 'พัฒนาเป็นโมดูลต่อเนื่องระหว่าง 19 ก.พ. – 29 พ.ค. 69 พร้อมทบทวนและแก้ไขระหว่างทาง'],
|
['Implementation', 'พัฒนาเป็นโมดูลต่อเนื่องระหว่าง 19 ก.พ. – 29 พ.ค. 69 พร้อมทบทวนและแก้ไขระหว่างทาง'],
|
||||||
['Verification', 'ตรวจสอบ Work Products 4 รอบ ควบคู่กับการพัฒนา และรอบสุดท้ายก่อนส่งมอบ'],
|
['Verification', 'ตรวจสอบ Work Products 4 รอบ ประมาณทุก 2 เดือน และรอบสุดท้ายก่อนส่งมอบ ตามแผนในหัวข้อ 8.1'],
|
||||||
['Validation', 'ทดสอบระบบและ UAT ระหว่าง 10–14 ส.ค. 69 บนสภาพแวดล้อมที่กำหนด'],
|
['Validation', 'ทดสอบระบบและ UAT ระหว่าง 10–14 ส.ค. 69 บนสภาพแวดล้อมที่กำหนด โดยใช้ Customer Requirements ทุกรายการเป็นตัวตั้ง'],
|
||||||
['Closure', 'ตรวจรับ อบรม ปิดงานควบคุมปฏิบัติการ และปิดโครงการภายใน 24 ส.ค. 69'],
|
['Closure', 'ตรวจรับ อบรม ปิดงานควบคุมปฏิบัติการ และปิดโครงการภายใน 24 ส.ค. 69'],
|
||||||
]),
|
]),
|
||||||
sec(5, 'Organization'),
|
sec(5, 'Organization'),
|
||||||
@@ -208,9 +211,9 @@ export function softwareProjectPlan() {
|
|||||||
[1, 'เอกสารบริหารโครงการ (PM Process)', '10 รายการ'],
|
[1, 'เอกสารบริหารโครงการ (PM Process)', '10 รายการ'],
|
||||||
[2, 'เอกสารกระบวนการพัฒนา (SI Process)', '12 รายการ'],
|
[2, 'เอกสารกระบวนการพัฒนา (SI Process)', '12 รายการ'],
|
||||||
[3, 'ความต้องการลูกค้า (Customer Requirements)', `${CRS.length} รายการ (CR01–CR14)`],
|
[3, 'ความต้องการลูกค้า (Customer Requirements)', `${CRS.length} รายการ (CR01–CR14)`],
|
||||||
[4, 'ความต้องการซอฟต์แวร์ (SRS)', '49 รายการ (SR01–SR09)'],
|
[4, 'ความต้องการซอฟต์แวร์ (SRS)', 'ประมาณ 50 รายการ แบ่งตามกลุ่มระบบในขอบเขตงาน'],
|
||||||
[5, 'Software Unit', '45 หน่วย (UN01–UN13)'],
|
[5, 'Software Unit', 'ประมาณ 45 หน่วย ใน 13 กลุ่มโมดูล'],
|
||||||
[6, 'Test Case', '45 รายการ'],
|
[6, 'Test Case', 'อย่างน้อย 1 รายการต่อ Software Unit'],
|
||||||
[7, 'ตารางฐานข้อมูล', 'ประมาณ 50 ตาราง ใน 2 ฐานข้อมูล'],
|
[7, 'ตารางฐานข้อมูล', 'ประมาณ 50 ตาราง ใน 2 ฐานข้อมูล'],
|
||||||
], ['c']),
|
], ['c']),
|
||||||
'### 6.2 Effort Man-Day',
|
'### 6.2 Effort Man-Day',
|
||||||
@@ -241,6 +244,19 @@ export function softwareProjectPlan() {
|
|||||||
['Verification & Validation', thRangeLong('2026-05-30', '2026-08-17'), 'Test Case, Test Report, Verification Results, Validation Results'],
|
['Verification & Validation', thRangeLong('2026-05-30', '2026-08-17'), 'Test Case, Test Report, Verification Results, Validation Results'],
|
||||||
['Project Close', thRangeLong('2026-08-10', END), 'Acceptance Report, Training Report, List of Evidence'],
|
['Project Close', thRangeLong('2026-08-10', END), 'Acceptance Report, Training Report, List of Evidence'],
|
||||||
]),
|
]),
|
||||||
|
'### 8.1 แผนการตรวจสอบ (Verification Plan)',
|
||||||
|
`โครงการมีระยะเวลา ${DURATION} วัน จึงกำหนดให้ตรวจสอบ Work Products ทั้งหมด ${VERIFICATION_ROUNDS.length} ครั้ง ประมาณทุก 2 เดือน (ทุก 60–75 วัน) ครั้งละ 3 ชั่วโมง และรอบสุดท้ายก่อนส่งมอบระบบ 6 ชั่วโมง เพื่อให้พบประเด็นตั้งแต่เนิ่น ๆ และแก้ไขได้ก่อนเริ่มงานขั้นถัดไป ผลการตรวจสอบแต่ละรอบบันทึกใน Verification Results และประเด็นที่พบบันทึกใน Correction Register`,
|
||||||
|
table(['รอบ', 'วันที่ตรวจสอบ', 'เวลา', 'ชั่วโมง', 'หัวข้อการตรวจสอบ', 'Deliverables under Review'],
|
||||||
|
VERIFICATION_ROUNDS.map((r) => [r.no, thLong(r.date), r.time, r.hours, r.topic, r.wps.join(', ')]), ['c', '', 'c', 'r', '', '']),
|
||||||
|
table(['หัวข้อ', 'รายละเอียด'], [
|
||||||
|
['ผู้ตรวจสอบ', `${P.PaNg.th} (QA/Tester) ร่วมกับ ${P.YaB.th} (Document Control)`],
|
||||||
|
['ผู้เข้าร่วมรับฟังผล', 'ผู้จัดทำ Work Product ที่ตรวจในรอบนั้น เพื่อแก้ไขได้ทันทีเมื่อพบประเด็น'],
|
||||||
|
['ผู้อนุมัติผลการตรวจสอบ', `${P.SeV.th} (Project Sponsor)`],
|
||||||
|
['รวมเวลาตรวจสอบตลอดโครงการ', `${VERIFICATION_ROUNDS.reduce((a, r) => a + r.hours, 0)} ชั่วโมง`],
|
||||||
|
['บันทึกผล', 'Verification Results แยกฉบับตามรอบ (V0.1–V1.0) และ Correction Register'],
|
||||||
|
]),
|
||||||
|
'**Risk & Constraints Note**',
|
||||||
|
numbered(VERIFICATION_RULES),
|
||||||
sec(9, 'Risk Management Plan'),
|
sec(9, 'Risk Management Plan'),
|
||||||
'วัตถุประสงค์ของแผนบริหารความเสี่ยง คือระบุแนวทางจัดการความเสี่ยงของโครงการ เพื่อให้โครงการบรรลุเป้าหมายภายในเวลา งบประมาณ และคุณภาพที่กำหนด',
|
'วัตถุประสงค์ของแผนบริหารความเสี่ยง คือระบุแนวทางจัดการความเสี่ยงของโครงการ เพื่อให้โครงการบรรลุเป้าหมายภายในเวลา งบประมาณ และคุณภาพที่กำหนด',
|
||||||
table(['ลำดับ', 'หมวด', 'รายการ', 'รายละเอียด'], [
|
table(['ลำดับ', 'หมวด', 'รายการ', 'รายละเอียด'], [
|
||||||
@@ -309,8 +325,8 @@ export function customerRequirements() {
|
|||||||
const date = '2026-02-06';
|
const date = '2026-02-06';
|
||||||
const rows = [];
|
const rows = [];
|
||||||
for (const [gid, gname, items] of CR_GROUPS) {
|
for (const [gid, gname, items] of CR_GROUPS) {
|
||||||
rows.push([`${gid}: ${gname}`, '', '', '']);
|
rows.push([`${gid}: ${gname}`, '', '']);
|
||||||
for (const [id, text, remark] of items) rows.push([id, text, 'A', remark]);
|
for (const [id, text] of items) rows.push([id, text, 'A']);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
dir: '1-PM Process (10 Work Product)/2.Project Plan/3-Customer Requirement',
|
dir: '1-PM Process (10 Work Product)/2.Project Plan/3-Customer Requirement',
|
||||||
@@ -336,7 +352,7 @@ export function customerRequirements() {
|
|||||||
[6, 'ฝ่ายบัญชีและการเงิน', 'ผู้ใช้งาน', 'ให้ข้อมูลการวางบิล รับชำระ จ่ายชำระ และการบันทึกบัญชี'],
|
[6, 'ฝ่ายบัญชีและการเงิน', 'ผู้ใช้งาน', 'ให้ข้อมูลการวางบิล รับชำระ จ่ายชำระ และการบันทึกบัญชี'],
|
||||||
], ['c']),
|
], ['c']),
|
||||||
'## ความต้องการเชิงหน้าที่และไม่ใช่หน้าที่ (Functional and Non-Functional Requirements)',
|
'## ความต้องการเชิงหน้าที่และไม่ใช่หน้าที่ (Functional and Non-Functional Requirements)',
|
||||||
table(['ID', 'Topic', 'Result*', 'Remark'], rows, ['', '', 'c', '']),
|
table(['ID', 'Topic', 'Result*'], rows, ['', '', 'c']),
|
||||||
'## Remark',
|
'## Remark',
|
||||||
bullets(['\\* Result : A = Accepted, U = Unaccepted, N/A = Not Applicable', 'เอกสารฉบับนี้รวมความต้องการที่ได้จากการทบทวนขอบเขตงานร่วมกับลูกค้า', 'การเปลี่ยนแปลงความต้องการหลังอนุมัติต้องดำเนินการผ่าน Change Report']),
|
bullets(['\\* Result : A = Accepted, U = Unaccepted, N/A = Not Applicable', 'เอกสารฉบับนี้รวมความต้องการที่ได้จากการทบทวนขอบเขตงานร่วมกับลูกค้า', 'การเปลี่ยนแปลงความต้องการหลังอนุมัติต้องดำเนินการผ่าน Change Report']),
|
||||||
].join('\n\n'),
|
].join('\n\n'),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// PM work products 3–10: Progress Status Record, Correction Register, Acceptance Report,
|
// PM work products 3–10: Progress Status Record, Correction Register, Acceptance Report,
|
||||||
// Change Report, Meeting Record, Software Configuration, Project Repository (+ Backup).
|
// Change Report, Meeting Record, Software Configuration, Project Repository (+ Backup).
|
||||||
import { P, 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, CHANGES, PERIODS, MEETINGS } from './history.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 { bud, thLong, thShort, thRange, thRangeLong, table, bullets, daysBetween } from './lib.mjs';
|
||||||
import { workProduct, head } from './format.mjs';
|
import { workProduct, head } from './format.mjs';
|
||||||
|
|
||||||
@@ -22,10 +22,8 @@ export function progressStatusRecords() {
|
|||||||
const c = CORRECTIONS.find((x) => x[0] === iid);
|
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, 'เสร็จสิ้น'];
|
return [n + 1, `${c[0]} : ${c[4]}`, `${c[5]} จึงดำเนินการ${c[6]} แล้วเสร็จ ${thShort(c[2])}`, P[c[7]].id, 'เสร็จสิ้น'];
|
||||||
});
|
});
|
||||||
const changes = changeIds.map((cid) => {
|
if (changeIds.length) throw new Error(`Progress record ${date} lists change requests, but the project has none.`);
|
||||||
const c = CHANGES.find((x) => x.id === cid);
|
const changes = [];
|
||||||
return [c.id, c.titleTh, 'อนุมัติ', c.impact[0][1]];
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
dir: dir3,
|
dir: dir3,
|
||||||
name: `${CODE} Progress Status Record ${bud(date)} V1.0 ApS`,
|
name: `${CODE} Progress Status Record ${bud(date)} V1.0 ApS`,
|
||||||
@@ -88,6 +86,10 @@ export function correctionRegister() {
|
|||||||
['คงค้าง', 0],
|
['คงค้าง', 0],
|
||||||
], ['', 'r']),
|
], ['', 'r']),
|
||||||
`ปัญหาทุกรายการได้รับการแก้ไขและตรวจสอบผลด้วย Test Case ที่เกี่ยวข้อง โดยผลการทดสอบผ่านทั้งหมดในรอบทดสอบระหว่าง ${thRangeLong(TEST_FROM, TEST_TO)} ไม่มีข้อบกพร่องระดับวิกฤตคงค้าง ณ วันตรวจรับ`,
|
`ปัญหาทุกรายการได้รับการแก้ไขและตรวจสอบผลด้วย 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']),
|
table(['เลขที่', 'การแก้ไขอ้างอิง (Commit)', 'Test Case ที่ใช้ตรวจสอบ', 'ผลการทดสอบ'], CORRECTIONS.map((c) => [c[0], `\`${c[8]}\``, c[9], 'Passed']), ['c', '', 'c', 'c']),
|
||||||
].join('\n\n'),
|
].join('\n\n'),
|
||||||
@@ -113,12 +115,12 @@ export function acceptanceReport() {
|
|||||||
body: [
|
body: [
|
||||||
'## รายการสิ่งส่งมอบ (Work Products)',
|
'## รายการสิ่งส่งมอบ (Work Products)',
|
||||||
table(['ลำดับ', 'รหัส', 'สิ่งส่งมอบ (Work Product)', 'รายละเอียด', 'ผลการตรวจรับ (Accepted / Not Accepted)'],
|
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(['ลำดับ', 'เกณฑ์การตรวจรับ', 'หลักฐานอ้างอิง', 'ผลการตรวจสอบ'], [
|
table(['ลำดับ', 'เกณฑ์การตรวจรับ', 'หลักฐานอ้างอิง', 'ผลการตรวจสอบ'], [
|
||||||
[1, 'ฟังก์ชันในขอบเขตทำงานตรงตาม Customer Requirements ครบทุกรายการ', 'Traceability Record, Test Report', 'ผ่าน'],
|
[1, 'ฟังก์ชันในขอบเขตทำงานตรงตาม Customer Requirements ครบทุกรายการ', 'Traceability Record, Test Report', 'ผ่าน'],
|
||||||
[2, 'ทดสอบระบบตาม Test Case ครบถ้วน', `Test Report 45 Test Case ผ่านทั้งหมด (${thRangeLong(TEST_FROM, TEST_TO)})`, 'ผ่าน'],
|
[2, 'ทดสอบระบบตาม Test Case ครบถ้วน', `Test Report 45 Test Case ผ่านทั้งหมด (${thRangeLong(TEST_FROM, TEST_TO)})`, 'ผ่าน'],
|
||||||
[3, 'ทดสอบการยอมรับโดยผู้ใช้ (UAT)', 'Validation Results 12 สถานการณ์ ผ่านทั้งหมด', 'ผ่าน'],
|
[3, 'ทดสอบการยอมรับโดยผู้ใช้ (UAT)', `Validation Results ครบ 14 หมวดความต้องการ (${CRS.length} รายการ) ผ่านทั้งหมด`, 'ผ่าน'],
|
||||||
[4, 'ไม่มีข้อบกพร่องระดับวิกฤตคงค้าง', `Correction Register ${CORRECTIONS.length} รายการ แก้ไขและตรวจสอบครบ`, 'ผ่าน'],
|
[4, 'ไม่มีข้อบกพร่องระดับวิกฤตคงค้าง', `Correction Register ${CORRECTIONS.length} รายการ แก้ไขและตรวจสอบครบ`, 'ผ่าน'],
|
||||||
[5, 'เอกสารคู่มือผู้ใช้ ผู้ดูแลระบบ และการบำรุงรักษาครบถ้วน', 'Software User Document, Product Operation Guide, Maintenance Document', 'ผ่าน'],
|
[5, 'เอกสารคู่มือผู้ใช้ ผู้ดูแลระบบ และการบำรุงรักษาครบถ้วน', 'Software User Document, Product Operation Guide, Maintenance Document', 'ผ่าน'],
|
||||||
[6, 'Source Code และเอกสารจัดเก็บใน Repository พร้อมชุดสำรอง', 'Project Repository, Project Repository (Backup)', 'ผ่าน'],
|
[6, 'Source Code และเอกสารจัดเก็บใน Repository พร้อมชุดสำรอง', 'Project Repository, Project Repository (Backup)', 'ผ่าน'],
|
||||||
@@ -132,72 +134,57 @@ export function acceptanceReport() {
|
|||||||
['ข้อบกพร่องคงค้าง', 'ไม่มี'],
|
['ข้อบกพร่องคงค้าง', 'ไม่มี'],
|
||||||
['ผลการตรวจรับโดยรวม', '**Accepted** — ตรวจรับส่งมอบระบบครบถ้วน'],
|
['ผลการตรวจรับโดยรวม', '**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'),
|
].join('\n\n'),
|
||||||
secretary: P.ApS, reviewer: P.YaB, approver: P.SeV,
|
secretary: P.ApS, reviewer: P.YaB, approver: P.SeV,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 6. Change Report — one document per change request. */
|
/** 6. Change Report — the change-control register. No adjustment in this project met the criteria. */
|
||||||
export function changeReports() {
|
export function changeReports() {
|
||||||
return CHANGES.map((c) => ({
|
const date = ACCEPT_DATE;
|
||||||
|
return [{
|
||||||
dir: '1-PM Process (10 Work Product)/6.Change Report',
|
dir: '1-PM Process (10 Work Product)/6.Change Report',
|
||||||
name: `${CODE} Change Report ${bud(c.date)} V1.0 ${c.requester.id}`,
|
name: `${CODE} Change Report ${bud(date)} V1.0 ApS`,
|
||||||
md: workProduct({
|
md: workProduct({
|
||||||
type: 'Change Report', footer: 'CR',
|
type: 'Change Report', footer: 'CR',
|
||||||
rows: head('Change Report', `${bud(c.date)} V1.0 ${c.requester.id}`, [
|
rows: head('Change Report', `${bud(date)} V1.0 ApS`, [
|
||||||
['Change ID', c.id, 'วันที่ร้องขอ', thLong(c.date)],
|
['Title', 'ทะเบียนการควบคุมการเปลี่ยนแปลง (Change Control Register)'],
|
||||||
['ผู้ร้องขอ (Requester)', `${c.requester.th} (${c.requester.role})`],
|
['Project Period', thRangeLong(START, END)],
|
||||||
['หน่วยงาน/ฝ่าย', c.unit],
|
['Organizer', ORGANIZER_PM],
|
||||||
['ประเภทการเปลี่ยนแปลง', `☒ ${c.type} ☒ ${c.typeChecked} ☐ Schedule ☐ Cost ☐ Resource ☐ Other`],
|
['Recorder', RECORDER],
|
||||||
['ความเร่งด่วน (Priority)', `☐ High ☒ ${c.priority} ☐ Low`],
|
|
||||||
]),
|
]),
|
||||||
body: [
|
body: [
|
||||||
'## ส่วนที่ 1 : ข้อมูลทั่วไป',
|
'## วัตถุประสงค์ (Objective)',
|
||||||
table(['หัวข้อ', 'รายละเอียด'], [
|
'เอกสารนี้บันทึกการควบคุมการเปลี่ยนแปลงของโครงการ ตั้งแต่ตั้ง Baseline ความต้องการเมื่อ 18 กุมภาพันธ์ 2569 จนถึงการตรวจรับส่งมอบ โดยพิจารณาทุกรายการปรับปรุงที่มีการเสนอตามเกณฑ์ในส่วนที่ 1 รายการที่เข้าเกณฑ์ต้องจัดทำคำขอเปลี่ยนแปลง ประเมินผลกระทบ และขออนุมัติก่อนดำเนินการ ส่วนรายการที่ไม่เข้าเกณฑ์ดำเนินการเป็นมติที่ประชุมหรืองานในแผน',
|
||||||
['ชื่อการเปลี่ยนแปลง', c.titleTh],
|
'## ส่วนที่ 1 : เกณฑ์การพิจารณาคำขอเปลี่ยนแปลง (Change Criteria)',
|
||||||
['โครงการ', PROJECT_TH],
|
'รายการใดจะถือเป็นคำขอเปลี่ยนแปลงเมื่อเข้าเกณฑ์ทั้ง 3 ข้อ หากตัดรายการนั้นออกแล้วยังส่งมอบงานได้ตามที่ตกลงไว้ จะไม่ถือเป็นคำขอเปลี่ยนแปลง',
|
||||||
['ระยะที่เกิดการเปลี่ยนแปลง', `ช่วงดำเนินโครงการ (${thLong(c.date)})`],
|
table(['ลำดับ', 'เกณฑ์', 'คำอธิบาย'], CHANGE_CRITERIA.map(([name, detail], i) => [i + 1, name, detail]), ['c']),
|
||||||
['เอกสารที่เกี่ยวข้อง', 'Customer Requirements, Software Project Plan, Traceability Record'],
|
'## ส่วนที่ 2 : ทะเบียนคำขอเปลี่ยนแปลง (Change Request Log)',
|
||||||
]),
|
table(['Change ID', 'หัวข้อ', 'ผู้ร้องขอ', 'ผลกระทบ', 'สถานะ'], [['-', 'ไม่มีคำขอเปลี่ยนแปลงที่เข้าเกณฑ์ตลอดโครงการ', '-', '-', '-']], ['c', '', 'c', '', 'c']),
|
||||||
'## ส่วนที่ 2 : รายละเอียดการเปลี่ยนแปลง',
|
'## ส่วนที่ 3 : รายการปรับปรุงที่พิจารณาแล้วไม่เข้าเกณฑ์',
|
||||||
table(['ลำดับ', 'รายการ', 'รายละเอียด'], [
|
table(['ลำดับ', 'วันที่พิจารณา', 'รายการ', 'ผู้เสนอ', 'ผลการพิจารณา', 'การดำเนินการ', 'อ้างอิง'],
|
||||||
[1, 'คำอธิบายการเปลี่ยนแปลงที่ต้องการ (Description)', c.description],
|
CHANGE_REVIEW.map(([when, topic, who, assessment, handled, ref], i) => [i + 1, thShort(when), topic, P[who].id, `ไม่เข้าเกณฑ์ — ${assessment}`, handled, ref]), ['c', 'c', '', 'c', '', '', '']),
|
||||||
[2, 'เหตุผลของการเปลี่ยนแปลง (Justification)', c.justification],
|
'## ส่วนที่ 4 : สรุปผลการควบคุมการเปลี่ยนแปลง',
|
||||||
], ['c']),
|
table(['รายการ', 'จำนวน'], [
|
||||||
'## ส่วนที่ 3 : การประเมินผลกระทบ (Impact Analysis)',
|
['รายการปรับปรุงที่เสนอและพิจารณา', CHANGE_REVIEW.length],
|
||||||
table(['ลำดับ', 'รายการ', 'รายละเอียด'], c.impact.map((r, i) => [i + 1, r[0], r[1]]), ['c']),
|
['คำขอเปลี่ยนแปลงที่เข้าเกณฑ์', 0],
|
||||||
'## ส่วนที่ 4 : การพิจารณาอนุมัติ (Change Advisory Board Approval)',
|
['คำขอเปลี่ยนแปลงที่อนุมัติ', 0],
|
||||||
table(['ลำดับ', 'ชื่อผู้พิจารณา', 'ตำแหน่ง', 'ความคิดเห็น / ลงนาม'], [
|
['ผลกระทบต่อ Man-day กำหนดส่งมอบ และค่าใช้จ่าย', 'ไม่มี'],
|
||||||
[1, P.ApS.th, 'Project Manager', 'เห็นควรอนุมัติ ผลกระทบอยู่ในวิสัยที่ควบคุมได้'],
|
], ['', 'r']),
|
||||||
[2, P.NoC.th, 'System Analyst', 'สอดคล้องกับความต้องการและการออกแบบ'],
|
'ขอบเขตงาน สิ่งส่งมอบ และ Customer Requirements ที่ตั้ง Baseline ไม่มีการเปลี่ยนแปลงตลอดโครงการ รายการปรับปรุงที่พิจารณาทั้งหมดดำเนินการภายในแผนงานเดิมโดยไม่กระทบ Man-day และกำหนดส่งมอบ',
|
||||||
[3, P.PaNg.th, 'QA / Tester', 'ต้องทดสอบซ้ำในส่วนที่ได้รับผลกระทบ'],
|
'## ภาคผนวก A : เกณฑ์การประเมินระดับความรุนแรงของการเปลี่ยนแปลง',
|
||||||
[4, P.SeV.th, 'Project Sponsor', 'อนุมัติ'],
|
'ใช้ประเมินระดับความรุนแรง (Priority Level) เมื่อมีคำขอเปลี่ยนแปลงที่เข้าเกณฑ์ในส่วนที่ 1',
|
||||||
], ['c']),
|
table(['ระดับ (Level)', 'คำจำกัดความ (Definition)', 'ตัวอย่างสถานการณ์ (Examples)'], [
|
||||||
'## ส่วนที่ 5 : สรุปผลการพิจารณา',
|
['High', 'กระทบระบบหลัก งบประมาณ หรือแผนงานอย่างมีนัยสำคัญ', 'เพิ่มโมดูลใหม่ เปลี่ยนโครงสร้างฐานข้อมูลหลัก'],
|
||||||
table(['หัวข้อ', 'รายละเอียด'], [
|
['Medium', 'กระทบฟังก์ชันย่อยหรือส่วนติดต่อผู้ใช้งานบางส่วน และกระทบ Man-day', 'รองรับ 2 ภาษา เพิ่มรายงานใหม่'],
|
||||||
['สถานะ', '☒ อนุมัติ (Approved) ☐ ไม่อนุมัติ (Rejected)'],
|
['Low', 'กระทบเล็กน้อยต่อ Man-day ไม่เปลี่ยนการทำงานหลัก', 'เพิ่มฟิลด์ในเอกสาร'],
|
||||||
['การอ้างอิงการดำเนินการ', `Commit \`${c.commit}\``],
|
|
||||||
['ผลการดำเนินการ', c.result],
|
|
||||||
]),
|
|
||||||
'## ภาคผนวก A : เกณฑ์การประเมินระดับความรุนแรงของการเปลี่ยนแปลง (Change Criteria)',
|
|
||||||
'ตารางนี้ใช้เป็นแนวทางประเมินระดับความรุนแรง (Priority Level) ของการเปลี่ยนแปลง เพื่อให้คณะพิจารณา (Change Advisory Board – CAB) กำหนดระดับผลกระทบและความเร่งด่วนได้ชัดเจนตามมาตรฐาน ISO/IEC 29110',
|
|
||||||
table(['ระดับ (Level)', 'คำจำกัดความ (Definition)', 'ตัวอย่างสถานการณ์ (Examples)', 'ผลกระทบ (Impact Scope)'], [
|
|
||||||
['High', 'การเปลี่ยนแปลงที่มีผลกระทบอย่างมีนัยสำคัญต่อระบบหลัก งบประมาณ หรือแผนโครงการ ซึ่งต้องได้รับอนุมัติจาก Project Manager และ Project Sponsor', 'ปรับสถาปัตยกรรมหลักของระบบ / เพิ่มฟังก์ชันหลักใหม่ / ต้องปรับงบประมาณหรือระยะเวลาโครงการ', 'กระทบ Scope, Schedule และ Cost โดยตรง'],
|
|
||||||
['Medium', 'การเปลี่ยนแปลงที่มีผลกระทบต่อส่วนติดต่อผู้ใช้งานหรือฟังก์ชันย่อยบางส่วน แต่ไม่กระทบโครงสร้างหลัก', 'ปรับปรุงหน้าจอ / ปรับข้อความหรือตรรกะบางส่วน / เพิ่มการตรวจสอบข้อมูล', 'กระทบ Quality หรือ Deliverables บางส่วน'],
|
|
||||||
['Low', 'การเปลี่ยนแปลงขนาดเล็กที่ไม่ส่งผลต่อการทำงานหลักของระบบ', 'แก้ไขคำสะกดหรือข้อความแสดงผล / เปลี่ยนโลโก้หรือรูปภาพ / ปรับฟอนต์หรือสี', 'ผลกระทบเล็กน้อยต่อ Deliverables'],
|
|
||||||
]),
|
|
||||||
'**แนวทางการใช้งาน**',
|
|
||||||
bullets([
|
|
||||||
'ผู้ร้องขอ (Requester) ระบุระดับความรุนแรงตามตารางนี้ในส่วน "ความเร่งด่วน (Priority)"',
|
|
||||||
'คณะพิจารณา (CAB) ใช้ตารางนี้ประกอบการพิจารณาในส่วน "การประเมินผลกระทบ (Impact Analysis)"',
|
|
||||||
'หากมีข้อสงสัยในระดับผลกระทบ ให้ใช้ระดับที่สูงกว่าเพื่อความปลอดภัยของโครงการ',
|
|
||||||
]),
|
]),
|
||||||
].join('\n\n'),
|
].join('\n\n'),
|
||||||
secretary: c.requester, reviewer: P.ApS, approver: P.SeV,
|
secretary: P.ApS, reviewer: P.YaB, approver: P.SeV,
|
||||||
}),
|
}),
|
||||||
}));
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 7. Meeting Record — Minutes of Meeting per checkpoint. */
|
/** 7. Meeting Record — Minutes of Meeting per checkpoint. */
|
||||||
@@ -266,7 +253,7 @@ export function softwareConfiguration() {
|
|||||||
['**เอกสารส่งมอบครั้งที่ 2 (พัฒนาและทดสอบ)**', '', ''],
|
['**เอกสารส่งมอบครั้งที่ 2 (พัฒนาและทดสอบ)**', '', ''],
|
||||||
['WP 3.0', `เอกสาร ${CODE} Software Requirements Specification`, '1.0'],
|
['WP 3.0', `เอกสาร ${CODE} Software Requirements Specification`, '1.0'],
|
||||||
['WP 4.0', `เอกสาร ${CODE} Software Design`, '1.0'],
|
['WP 4.0', `เอกสาร ${CODE} Software Design`, '1.0'],
|
||||||
['WP 5.0', `เอกสาร ${CODE} Change Report (CH-001–CH-003)`, '1.0'],
|
['WP 5.0', `เอกสาร ${CODE} Change Report`, '1.0'],
|
||||||
['WP 6.0', `เอกสาร ${CODE} Test Case and Test Procedures`, '1.0'],
|
['WP 6.0', `เอกสาร ${CODE} Test Case and Test Procedures`, '1.0'],
|
||||||
['WP 7.0', `เอกสาร ${CODE} Validation Results`, '1.0'],
|
['WP 7.0', `เอกสาร ${CODE} Validation Results`, '1.0'],
|
||||||
['WP 8.0', `เอกสาร ${CODE} Software User Document`, '1.0'],
|
['WP 8.0', `เอกสาร ${CODE} Software User Document`, '1.0'],
|
||||||
@@ -292,11 +279,10 @@ export function softwareConfiguration() {
|
|||||||
['Repository สำรอง (backup)', `\`${BACKUP}\``],
|
['Repository สำรอง (backup)', `\`${BACKUP}\``],
|
||||||
['Branch หลัก', '`main` เป็น Baseline ที่ควบคุม'],
|
['Branch หลัก', '`main` เป็น Baseline ที่ควบคุม'],
|
||||||
['Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\``],
|
['Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\``],
|
||||||
['Tag ของชุดเอกสารส่งมอบ', `\`${TAG}\``],
|
|
||||||
['สิทธิ์การเข้าถึง', 'ยืนยันตัวตนด้วย SSH Key ตามบทบาทของสมาชิกโครงการ'],
|
['สิทธิ์การเข้าถึง', 'ยืนยันตัวตนด้วย SSH Key ตามบทบาทของสมาชิกโครงการ'],
|
||||||
]),
|
]),
|
||||||
'## การเชื่อมโยงกับการเปลี่ยนแปลง',
|
'## การเชื่อมโยงกับการเปลี่ยนแปลง',
|
||||||
`การเปลี่ยนแปลง Configuration Item ต้องผ่าน Change Report (CH-001–CH-003) และการแก้ไขข้อบกพร่องต้องบันทึกใน Correction Register (${CORRECTIONS[0][0]}–${CORRECTIONS[CORRECTIONS.length - 1][0]}) โดยทุกรายการเชื่อมโยงกับ Commit ที่ตรวจสอบได้ใน Repository`,
|
`การเปลี่ยนแปลง Configuration Item ต้องพิจารณาตามเกณฑ์ใน Change Report และการแก้ไขข้อบกพร่องต้องบันทึกใน Correction Register (${CORRECTIONS[0][0]}–${CORRECTIONS[CORRECTIONS.length - 1][0]}) โดยทุกรายการเชื่อมโยงกับ Commit ที่ตรวจสอบได้ใน Repository`,
|
||||||
].join('\n\n'),
|
].join('\n\n'),
|
||||||
secretary: P.ThS, reviewer: P.ApS, approver: P.SeV,
|
secretary: P.ThS, reviewer: P.ApS, approver: P.SeV,
|
||||||
}),
|
}),
|
||||||
@@ -323,9 +309,7 @@ export function projectRepository() {
|
|||||||
['ประเภท', 'Git Server'],
|
['ประเภท', 'Git Server'],
|
||||||
['Repository หลัก (origin)', `\`${ORIGIN}\``],
|
['Repository หลัก (origin)', `\`${ORIGIN}\``],
|
||||||
['Branch หลัก', '`main`'],
|
['Branch หลัก', '`main`'],
|
||||||
['Branch เอกสารส่งมอบ', '`sdlc`'],
|
|
||||||
['Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\``],
|
['Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\``],
|
||||||
['Tag ของชุดเอกสารส่งมอบ', `\`${TAG}\``],
|
|
||||||
['สิทธิ์การเข้าถึง', 'ยืนยันตัวตนด้วย SSH Key ตามบทบาทของสมาชิกโครงการ'],
|
['สิทธิ์การเข้าถึง', 'ยืนยันตัวตนด้วย SSH Key ตามบทบาทของสมาชิกโครงการ'],
|
||||||
]),
|
]),
|
||||||
'## โครงสร้างการจัดเก็บ',
|
'## โครงสร้างการจัดเก็บ',
|
||||||
@@ -349,7 +333,7 @@ export function projectRepository() {
|
|||||||
table(['ลำดับ', 'หลักฐาน', 'รายละเอียด'], [
|
table(['ลำดับ', 'หลักฐาน', 'รายละเอียด'], [
|
||||||
[1, 'โครงสร้าง Repository และประวัติการเปลี่ยนแปลง', 'ตรวจสอบได้จาก Git Server ตามสิทธิ์ที่ได้รับ'],
|
[1, 'โครงสร้าง Repository และประวัติการเปลี่ยนแปลง', 'ตรวจสอบได้จาก Git Server ตามสิทธิ์ที่ได้รับ'],
|
||||||
[2, 'Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\` บน branch \`main\``],
|
[2, 'Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\` บน branch \`main\``],
|
||||||
[3, 'ชุดเอกสารส่งมอบที่ควบคุมแล้ว', `Branch \`sdlc\` และ Tag \`${TAG}\``],
|
[3, 'ชุดเอกสารส่งมอบที่ควบคุมแล้ว', 'Git Repository'],
|
||||||
], ['c']),
|
], ['c']),
|
||||||
].join('\n\n'),
|
].join('\n\n'),
|
||||||
secretary: P.ThS, reviewer: P.ApS, approver: P.SeV,
|
secretary: P.ThS, reviewer: P.ApS, approver: P.SeV,
|
||||||
@@ -377,7 +361,7 @@ export function projectRepositoryBackup() {
|
|||||||
['วิธีการสำรอง', 'Git Remote สำรอง (Secondary Remote)'],
|
['วิธีการสำรอง', 'Git Remote สำรอง (Secondary Remote)'],
|
||||||
['ชื่อ Remote สำรอง', '`backup`'],
|
['ชื่อ Remote สำรอง', '`backup`'],
|
||||||
['ที่อยู่ Remote สำรอง', `\`${BACKUP}\``],
|
['ที่อยู่ Remote สำรอง', `\`${BACKUP}\``],
|
||||||
['ขอบเขตการสำรอง', `Branch \`main\` (Baseline \`${BASELINE_COMMIT}\`), Branch \`sdlc\` และ Tag \`${TAG}\``],
|
['ขอบเขตการสำรอง', `Branch \`main\` (Baseline \`${BASELINE_COMMIT}\`) และเอกสารส่งมอบใน Git Repository`],
|
||||||
['รอบการสำรอง', 'ซิงก์ทุกครั้งที่มีการส่งมอบหรือปรับปรุง Baseline'],
|
['รอบการสำรอง', 'ซิงก์ทุกครั้งที่มีการส่งมอบหรือปรับปรุง Baseline'],
|
||||||
['ผู้รับผิดชอบ', `${P.ThS.th} (Developer)`],
|
['ผู้รับผิดชอบ', `${P.ThS.th} (Developer)`],
|
||||||
]),
|
]),
|
||||||
@@ -394,14 +378,14 @@ export function projectRepositoryBackup() {
|
|||||||
['BK-001', `ตรวจสอบว่า Remote สำรองเข้าถึงได้และมี Baseline \`${BASELINE_COMMIT}\` ตรงกับ Repository หลัก`, P.ThS.id, thLong('2026-08-17'), 'ผ่าน'],
|
['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-002', 'ทดสอบ Clone จาก Remote สำรองและตรวจสอบความครบถ้วนของข้อมูล', P.ThS.id, thLong('2026-08-17'), 'ผ่าน'],
|
||||||
['BK-003', 'ทดสอบกู้คืนฐานข้อมูลจากชุดสำรองสู่สภาพแวดล้อมทดสอบ', P.ThS.id, thLong(OPS_CLOSE_DATE), 'ผ่าน'],
|
['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']),
|
], ['c', '', 'c', 'c', 'c']),
|
||||||
'## ขั้นตอนการกู้คืน',
|
'## ขั้นตอนการกู้คืน',
|
||||||
table(['ลำดับ', 'ขั้นตอน', 'รายละเอียด'], [
|
table(['ลำดับ', 'ขั้นตอน', 'รายละเอียด'], [
|
||||||
[1, 'ยืนยันขอบเขตความเสียหาย', 'ตรวจสอบว่าข้อมูลที่สูญหายเป็น Source Code เอกสาร หรือฐานข้อมูล'],
|
[1, 'ยืนยันขอบเขตความเสียหาย', 'ตรวจสอบว่าข้อมูลที่สูญหายเป็น Source Code เอกสาร หรือฐานข้อมูล'],
|
||||||
[2, 'เลือกชุดสำรองที่จะใช้', 'เลือกชุดสำรองล่าสุดที่ผ่านการตรวจสอบและยืนยันวันที่'],
|
[2, 'เลือกชุดสำรองที่จะใช้', 'เลือกชุดสำรองล่าสุดที่ผ่านการตรวจสอบและยืนยันวันที่'],
|
||||||
[3, 'กู้คืนสู่สภาพแวดล้อมทดสอบก่อน', 'Clone จาก Remote สำรอง หรือ Restore ฐานข้อมูลสู่เครื่องทดสอบ'],
|
[3, 'กู้คืนสู่สภาพแวดล้อมทดสอบก่อน', 'Clone จาก Remote สำรอง หรือ Restore ฐานข้อมูลสู่เครื่องทดสอบ'],
|
||||||
[4, 'ตรวจสอบความถูกต้อง', 'ตรวจสอบ Baseline, Tag และข้อมูลตัวอย่างว่าครบถ้วน'],
|
[4, 'ตรวจสอบความถูกต้อง', 'ตรวจสอบ Baseline และข้อมูลตัวอย่างว่าครบถ้วน'],
|
||||||
[5, 'กู้คืนสู่สภาพแวดล้อมใช้งานจริง', 'บันทึกจุดกู้คืน หยุดบริการที่เกี่ยวข้อง กู้คืน แล้วตรวจสอบสถานะระบบตามคู่มือผู้ดูแลระบบ'],
|
[5, 'กู้คืนสู่สภาพแวดล้อมใช้งานจริง', 'บันทึกจุดกู้คืน หยุดบริการที่เกี่ยวข้อง กู้คืน แล้วตรวจสอบสถานะระบบตามคู่มือผู้ดูแลระบบ'],
|
||||||
], ['c']),
|
], ['c']),
|
||||||
].join('\n\n'),
|
].join('\n\n'),
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// SI work products 11–22.
|
// 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 { SR_GROUPS, SRS, srTopic, UNIT_GROUPS, UNITS, unitTopic, unitModule, TEST_CASES, traceOf } from './engineering.mjs';
|
||||||
import { CORRECTIONS } from './history.mjs';
|
import { CORRECTIONS } from './history.mjs';
|
||||||
import { bud, thLong, thShort, thRangeLong, table, bullets, numbered } from './lib.mjs';
|
import { figure } from './figures.mjs';
|
||||||
|
import { bud, thLong, thShort, thRange, thRangeLong, table, bullets, numbered } from './lib.mjs';
|
||||||
import { workProduct, head } from './format.mjs';
|
import { workProduct, head } from './format.mjs';
|
||||||
|
|
||||||
const D = '2026-08-17';
|
const D = '2026-08-17';
|
||||||
@@ -72,8 +73,12 @@ export function softwareDesign() {
|
|||||||
]),
|
]),
|
||||||
body: [
|
body: [
|
||||||
'## HIGH LEVEL DESIGN',
|
'## HIGH LEVEL DESIGN',
|
||||||
|
'### High Level Architecture Diagram',
|
||||||
|
'ภาพรวมสถาปัตยกรรมของระบบ แบ่งเป็นผู้ใช้งาน ชั้น Client, Web, Data, Service และบริการภายนอก พร้อมช่องทางการเชื่อมต่อระหว่างกัน ทุกคำขอจากหน้าจอผ่านการตรวจสอบของ db_auth ก่อนถึงตรรกะธุรกิจ',
|
||||||
|
figure('architecture'),
|
||||||
'### Use Case Diagram',
|
'### Use Case Diagram',
|
||||||
'ผู้ใช้งานของระบบและกลุ่มการใช้งานหลัก',
|
'ผู้ใช้งานของระบบและกลุ่มการใช้งานหลัก บทบาทที่สูงกว่าได้รับสิทธิ์การใช้งานของบทบาทที่ต่ำกว่าทั้งหมด (Viewer → Staff → Admin → Owner)',
|
||||||
|
figure('useCase'),
|
||||||
table(['Actor', 'คำอธิบาย', 'การใช้งานหลัก'], [
|
table(['Actor', 'คำอธิบาย', 'การใช้งานหลัก'], [
|
||||||
['Owner', 'เจ้าของบริษัท สิทธิ์สูงสุดภายในบริษัท', 'ตั้งค่าบริษัท จัดการผู้ใช้ และใช้งานทุกโมดูล'],
|
['Owner', 'เจ้าของบริษัท สิทธิ์สูงสุดภายในบริษัท', 'ตั้งค่าบริษัท จัดการผู้ใช้ และใช้งานทุกโมดูล'],
|
||||||
['Admin', 'ผู้ดูแลระบบภายในบริษัท', 'จัดการข้อมูลหลัก ผู้ใช้ สิทธิ์ และใช้งานทุกโมดูลปฏิบัติการ'],
|
['Admin', 'ผู้ดูแลระบบภายในบริษัท', 'จัดการข้อมูลหลัก ผู้ใช้ สิทธิ์ และใช้งานทุกโมดูลปฏิบัติการ'],
|
||||||
@@ -83,6 +88,8 @@ export function softwareDesign() {
|
|||||||
['SMTP Service', 'ผู้กระทำภายนอก', 'ส่งอีเมล Onboarding กู้คืนรหัสผ่าน และแจ้งเตือน'],
|
['SMTP Service', 'ผู้กระทำภายนอก', 'ส่งอีเมล Onboarding กู้คืนรหัสผ่าน และแจ้งเตือน'],
|
||||||
]),
|
]),
|
||||||
'### Component Diagram',
|
'### Component Diagram',
|
||||||
|
'โมดูลของระบบ (Software Unit กลุ่ม UN01–UN13) และความสัมพันธ์การเรียกใช้ระหว่างโมดูล',
|
||||||
|
figure('component'),
|
||||||
'โครงสร้างส่วนประกอบของระบบแบ่งตามชั้นการทำงาน',
|
'โครงสร้างส่วนประกอบของระบบแบ่งตามชั้นการทำงาน',
|
||||||
table(['ชั้น (Layer)', 'ส่วนประกอบ', 'อ้างอิง SR'], [
|
table(['ชั้น (Layer)', 'ส่วนประกอบ', 'อ้างอิง SR'], [
|
||||||
['Presentation', 'หน้าจอ PHP ในแต่ละโมดูล (`app/<module>/`), JavaScript และ CSS ที่รองรับ Responsive', 'SR02:002, SR06:002, SR06:003'],
|
['Presentation', 'หน้าจอ PHP ในแต่ละโมดูล (`app/<module>/`), JavaScript และ CSS ที่รองรับ Responsive', 'SR02:002, SR06:002, SR06:003'],
|
||||||
@@ -92,6 +99,8 @@ export function softwareDesign() {
|
|||||||
['External', 'บริการ SMTP สำหรับส่งอีเมล และ Socket.IO Client บน Browser', 'SR06:004, SR06:005'],
|
['External', 'บริการ SMTP สำหรับส่งอีเมล และ Socket.IO Client บน Browser', 'SR06:004, SR06:005'],
|
||||||
]),
|
]),
|
||||||
'### Deployment Diagram',
|
'### Deployment Diagram',
|
||||||
|
'การติดตั้งบนสภาพแวดล้อมใช้งานจริงด้วย Docker Compose และทางเลือกการติดตั้งแบบ Manual',
|
||||||
|
figure('deployment'),
|
||||||
'การติดตั้งระบบแบ่งเป็น 4 ชั้นบริการ',
|
'การติดตั้งระบบแบ่งเป็น 4 ชั้นบริการ',
|
||||||
table(['ชั้นการติดตั้ง (Tier)', 'ส่วนประกอบ', 'อ้างอิง SR'], [
|
table(['ชั้นการติดตั้ง (Tier)', 'ส่วนประกอบ', 'อ้างอิง SR'], [
|
||||||
['Client', 'Web Browser (Chrome/Edge/Firefox) บน Desktop, Tablet และ Mobile พร้อม Socket.IO Client', 'SR06:002, SR06:003, SR06:005'],
|
['Client', 'Web Browser (Chrome/Edge/Firefox) บน Desktop, Tablet และ Mobile พร้อม Socket.IO Client', 'SR06:002, SR06:003, SR06:005'],
|
||||||
@@ -100,7 +109,7 @@ export function softwareDesign() {
|
|||||||
['Service Tier', 'Node.js `server.js` และ `scheduler.js` ควบคุมด้วย pm2', 'SR03:011, SR06:004'],
|
['Service Tier', 'Node.js `server.js` และ `scheduler.js` ควบคุมด้วย pm2', 'SR03:011, SR06:004'],
|
||||||
]),
|
]),
|
||||||
'### User Interface Design',
|
'### User Interface Design',
|
||||||
'ผังหน้าจอหลักของระบบแบ่งตามโมดูลการใช้งาน',
|
'ผังหน้าจอหลักของระบบแบ่งตามโมดูลการใช้งาน ทุกหน้าจอใช้โครงร่างเดียวกัน ได้แก่ เมนูด้านซ้ายที่แสดงตามสิทธิ์ของผู้ใช้ แถบด้านบนสำหรับเลือกบริษัทและบัญชีผู้ใช้ และพื้นที่ทำงานด้านขวา',
|
||||||
table(['กลุ่มหน้าจอ', 'หน้าจอ'], [
|
table(['กลุ่มหน้าจอ', 'หน้าจอ'], [
|
||||||
['Login & Onboarding', 'เข้าสู่ระบบ / ลงทะเบียนบริษัท / รับคำเชิญเข้าร่วมบริษัท / ลืมรหัสผ่าน / ยืนยัน OTP'],
|
['Login & Onboarding', 'เข้าสู่ระบบ / ลงทะเบียนบริษัท / รับคำเชิญเข้าร่วมบริษัท / ลืมรหัสผ่าน / ยืนยัน OTP'],
|
||||||
['Dashboard', 'แดชบอร์ดคลังสินค้า / แดชบอร์ดบัญชีและการเงิน'],
|
['Dashboard', 'แดชบอร์ดคลังสินค้า / แดชบอร์ดบัญชีและการเงิน'],
|
||||||
@@ -113,22 +122,30 @@ export function softwareDesign() {
|
|||||||
['Reports', 'รายงานสต๊อก / ความเคลื่อนไหว / ความจุคลัง / สินค้าใกล้หมด / สินค้าหมดอายุ / รายงานการเงิน'],
|
['Reports', 'รายงานสต๊อก / ความเคลื่อนไหว / ความจุคลัง / สินค้าใกล้หมด / สินค้าหมดอายุ / รายงานการเงิน'],
|
||||||
['Settings', 'ข้อมูลบริษัท / SMTP / ผู้ใช้งานและสิทธิ์ / การตั้งค่าระบบ'],
|
['Settings', 'ข้อมูลบริษัท / SMTP / ผู้ใช้งานและสิทธิ์ / การตั้งค่าระบบ'],
|
||||||
]),
|
]),
|
||||||
|
'### Mockup Wireframe',
|
||||||
|
'Wireframe แสดงโครงร่างหน้าจอหลักที่ใช้เป็นแม่แบบในการพัฒนา หน้าจอเอกสารขาย จัดซื้อ และการเงินใช้รูปแบบหน้ารายการ (รูปที่ 7) และหน้าบันทึก (รูปที่ 8) เดียวกัน',
|
||||||
|
figure('wfLogin'),
|
||||||
|
figure('wfDashboard'),
|
||||||
|
figure('wfList'),
|
||||||
|
figure('wfDocument'),
|
||||||
|
figure('wfStockIn'),
|
||||||
|
figure('wfReport'),
|
||||||
'## Software Baseline',
|
'## Software Baseline',
|
||||||
'หัวข้อนี้ระบุสิ่งที่ถูกกำหนดเป็น Baseline ของซอฟต์แวร์ วันที่กำหนด และผู้อนุมัติ เพื่อให้ตรวจสอบเส้นทาง Baseline → Configuration → Change ได้ในเอกสารชุดเดียวกัน',
|
'หัวข้อนี้กำหนดสิ่งที่จะควบคุมเป็น Baseline ของซอฟต์แวร์ เวลาที่กำหนด Baseline และผู้อนุมัติ เพื่อให้ตรวจสอบเส้นทาง Baseline → Configuration → Change ได้ Commit ที่เป็น Baseline จริงจะบันทึกใน Software Configuration (WP 8) เมื่อกำหนด Baseline แต่ละครั้ง',
|
||||||
table(['รายการที่กำหนดเป็น 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 เดียวกัน'],
|
['โครงสร้างฐานข้อมูล', 'Schema ที่สร้างโดย `setup.php` และ `docker/mariadb/init-wms2.sql`', 'ควบคุมใน Repository เดียวกัน'],
|
||||||
['ค่าตั้งค่าและชุดติดตั้ง', '`docker-compose.yml`, `docker/`, `.env.example` (ไม่รวมค่าความลับ)', 'ควบคุมใน 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(['หัวข้อ', 'รายละเอียด'], [
|
table(['หัวข้อ', 'รายละเอียด'], [
|
||||||
['วันที่กำหนด Baseline', `${thLong('2026-08-17')} (Baseline ของระบบที่ส่งมอบ)`],
|
['เวลาที่กำหนด Baseline', `เมื่อพัฒนาครบทุกโมดูล (Baseline การพัฒนา ตามแผน ${thLong(DEV_BASELINE)}) และเมื่อตรวจรับส่งมอบ (Baseline ของระบบที่ส่งมอบ ตามแผน ${thLong(ACCEPT_DATE)})`],
|
||||||
['ผู้จัดทำ Baseline', `${P.ThS.th} (Developer)`],
|
['ผู้จัดทำ Baseline', `${P.ThS.th} (Developer)`],
|
||||||
['ผู้ตรวจสอบ', `${P.NoC.th} (System Analyst) และ ${P.PaNg.th} (QA/Tester)`],
|
['ผู้ตรวจสอบ', `${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 และการควบคุมเวอร์ชัน'],
|
['เอกสารควบคุมที่เกี่ยวข้อง', '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 (CH-001–CH-003) และบันทึกผลใน Correction Register เมื่อเป็นการแก้ไขข้อบกพร่อง'],
|
['การเปลี่ยนแปลง Baseline', 'ต้องพิจารณาตามเกณฑ์ใน Change Report และบันทึกผลใน Correction Register เมื่อเป็นการแก้ไขข้อบกพร่อง'],
|
||||||
]),
|
]),
|
||||||
'## Software Unit',
|
'## Software Unit',
|
||||||
table(['ID', 'Description', 'Functional Interfaces Detail', 'References', 'Files'], unitRows),
|
table(['ID', 'Description', 'Functional Interfaces Detail', 'References', 'Files'], unitRows),
|
||||||
@@ -143,12 +160,15 @@ export function traceabilityRecord() {
|
|||||||
const date = '2026-03-06';
|
const date = '2026-03-06';
|
||||||
const rows = [];
|
const rows = [];
|
||||||
let group = '';
|
let group = '';
|
||||||
for (const [id, text, remark] of CRS) {
|
for (const [id, text] of CRS) {
|
||||||
const g = id.slice(0, 4);
|
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);
|
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]);
|
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 byDocument = traced.filter((t) => !t.un.length).length;
|
||||||
return {
|
return {
|
||||||
dir: '2-SI Process (12 Work Product)/13.Traceability record',
|
dir: '2-SI Process (12 Work Product)/13.Traceability record',
|
||||||
name: `${CODE} Traceability Record ${bud(date)} V1.0 NoC`,
|
name: `${CODE} Traceability Record ${bud(date)} V1.0 NoC`,
|
||||||
@@ -162,20 +182,20 @@ export function traceabilityRecord() {
|
|||||||
]),
|
]),
|
||||||
body: [
|
body: [
|
||||||
'## วัตถุประสงค์ (Objective)',
|
'## วัตถุประสงค์ (Objective)',
|
||||||
'เอกสารที่เชื่อมโยงทุกขั้นตอนของโครงการ ตั้งแต่ความต้องการของลูกค้า → ความต้องการซอฟต์แวร์ → การออกแบบ → การทดสอบ เพื่อยืนยันความครบถ้วนของ Customer Requirements และลดความเสี่ยงของข้อบกพร่องและความต้องการที่ตกหล่น',
|
'เอกสารที่เชื่อมโยงความต้องการของลูกค้า → ความต้องการซอฟต์แวร์ → การออกแบบ (Software Unit) ณ วันที่ออกแบบระบบ เพื่อยืนยันความครบถ้วนของ Customer Requirements ก่อนเริ่มพัฒนา และลดความเสี่ยงของความต้องการที่ตกหล่น การสอบกลับไปยัง Test Case และผลการทดสอบจะเพิ่มใน Traceability Record ฉบับส่งมอบ หลังจัดทำ Test Case and Test Procedures',
|
||||||
'## Traceability Matrix',
|
'## Traceability Matrix',
|
||||||
table(['CR ID', 'CR Topic', 'SRS ID', 'SRS Topic', 'Unit ID', 'Unit Topic', 'Test Case ID'], rows),
|
table(['CR ID', 'CR Topic', 'SRS ID', 'SRS Topic', 'Unit ID', 'Unit Topic', 'หลักฐานอื่น (Document)'], rows),
|
||||||
|
'หมายเหตุ: ความต้องการที่เป็นเอกสาร การอบรม หรือหลักฐานด้านคุณภาพ สอบกลับไปยังเอกสารส่งมอบที่เป็นหลักฐานในคอลัมน์ "หลักฐานอื่น" แทน Software Unit',
|
||||||
'## สรุปความครอบคลุม',
|
'## สรุปความครอบคลุม',
|
||||||
table(['รายการ', 'จำนวน'], [
|
table(['รายการ', 'จำนวน'], [
|
||||||
['ความต้องการของลูกค้าทั้งหมด (CR01–CR14)', CRS.length],
|
['ความต้องการของลูกค้าทั้งหมด (CR01–CR14)', CRS.length],
|
||||||
['เชื่อมโยงกับความต้องการซอฟต์แวร์ (SRS)', CRS.length],
|
['เชื่อมโยงกับความต้องการซอฟต์แวร์ (SRS)', CRS.length],
|
||||||
['เชื่อมโยงกับ Software Unit', CRS.length],
|
['เชื่อมโยงกับ Software Unit', withUnits],
|
||||||
['เชื่อมโยงกับ Test Case', CRS.length],
|
['ยืนยันด้วยเอกสารส่งมอบ (ไม่มี Software Unit)', byDocument],
|
||||||
['ความต้องการซอฟต์แวร์ทั้งหมด (SR01–SR09)', SRS.length],
|
['ความต้องการซอฟต์แวร์ทั้งหมด (SR01–SR09)', SRS.length],
|
||||||
['Software Unit ทั้งหมด', UNITS.length],
|
['Software Unit ทั้งหมด', UNITS.length],
|
||||||
['Test Case ทั้งหมด', TEST_CASES.length],
|
|
||||||
], ['', 'r']),
|
], ['', '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'),
|
].join('\n\n'),
|
||||||
secretary: P.NoC, reviewer: P.YaB, approver: P.SeV,
|
secretary: P.NoC, reviewer: P.YaB, approver: P.SeV,
|
||||||
}),
|
}),
|
||||||
@@ -209,7 +229,7 @@ export function softwareComponents() {
|
|||||||
[4, 'Deployment Package', 'ชุดติดตั้ง Docker Compose (php-apache, mariadb, node/pm2)', '`docker-compose.yml`, `docker/`', `\`${BASELINE_COMMIT}\``],
|
[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}\``],
|
[5, 'Configuration Template', 'แม่แบบค่าตั้งค่าและสคริปต์สร้าง `.env`', '`.env.example`, `docker/init-env.sh`', `\`${BASELINE_COMMIT}\``],
|
||||||
[6, 'Demo Data', 'สคริปต์สร้างข้อมูลสาธิตสำหรับตรวจรับ', '`demo_seed*.php`', '`dd48a8b`'],
|
[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']),
|
], ['c']),
|
||||||
'## รายการ Software Unit ที่พัฒนาแล้ว',
|
'## รายการ Software Unit ที่พัฒนาแล้ว',
|
||||||
table(['Module', 'Unit ID', 'Description', 'สถานะ'],
|
table(['Module', 'Unit ID', 'Description', 'สถานะ'],
|
||||||
@@ -236,7 +256,7 @@ export function softwareComponents() {
|
|||||||
/** 15. Test Cases and Test Procedures. */
|
/** 15. Test Cases and Test Procedures. */
|
||||||
export function testCases() {
|
export function testCases() {
|
||||||
const date = '2026-07-31';
|
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 {
|
return {
|
||||||
dir: '2-SI Process (12 Work Product)/15.Test Cases and Test Procedures',
|
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`,
|
name: `${CODE} Test Case and Test Procedures ${bud(date)} V1.0 PaNg`,
|
||||||
@@ -257,14 +277,14 @@ export function testCases() {
|
|||||||
['ทดสอบระบบตาม Test Case', 'Internal Testing Server (PHP 8, MariaDB, Node.js)', `${P.PaNg.th} (QA/Tester)`],
|
['ทดสอบระบบตาม Test Case', 'Internal Testing Server (PHP 8, MariaDB, Node.js)', `${P.PaNg.th} (QA/Tester)`],
|
||||||
['ทดสอบการยอมรับ (UAT)', 'สภาพแวดล้อมใช้งานจริงของลูกค้า', `${P.SeV.th} (Project Sponsor / ตัวแทนลูกค้า)`],
|
['ทดสอบการยอมรับ (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',
|
'## ตาราง 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']),
|
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',
|
'## Summary',
|
||||||
table(['No', 'Topic', 'Detail'], [
|
table(['No', 'Topic', 'Detail'], [
|
||||||
[1, 'Test Case', `การทดสอบระบบทั้งหมด ${TEST_CASES.length} Test Case ผ่าน 100%`],
|
[1, 'Test Case', `กำหนด Test Case ทั้งหมด ${TEST_CASES.length} รายการ ครอบคลุม Software Unit ทุกหน่วย สถานะพร้อมทดสอบ (Ready)`],
|
||||||
[2, 'Pending', 'ไม่พบข้อผิดพลาด (Fail) หรือรายการค้าง (Pending) ในรอบการทดสอบนี้'],
|
[2, 'เกณฑ์ผ่าน', 'ทุก Test Case ต้องได้ผลตาม Output Specifications ข้อบกพร่องที่พบบันทึกใน Correction Register และทดสอบซ้ำจนผ่าน'],
|
||||||
[3, 'Conclusion', 'ผลการทดสอบสรุปได้ว่าระบบพร้อมสำหรับ UAT และการส่งมอบ เนื่องจากผลการทดสอบผ่านครบ 100% และเป็นไปตาม Project Plan'],
|
[3, 'เกณฑ์เข้าสู่ UAT', 'Test Case ผ่านครบ 100% และไม่มีข้อบกพร่องระดับสูงคงค้าง ผลการทดสอบบันทึกใน Test Report'],
|
||||||
], ['c']),
|
], ['c']),
|
||||||
].join('\n\n'),
|
].join('\n\n'),
|
||||||
secretary: P.PaNg, reviewer: P.NoC, approver: P.SeV,
|
secretary: P.PaNg, reviewer: P.NoC, approver: P.SeV,
|
||||||
@@ -275,9 +295,11 @@ export function testCases() {
|
|||||||
/** 16. Test Report. */
|
/** 16. Test Report. */
|
||||||
export function testReport() {
|
export function testReport() {
|
||||||
const date = '2026-08-14';
|
const date = '2026-08-14';
|
||||||
const rows = CRS.map(([id, text], i) => {
|
const tested = CRS.filter(([id]) => traceOf(id).tcs.length);
|
||||||
|
const byDocument = CRS.length - tested.length;
|
||||||
|
const rows = tested.map(([id, text], i) => {
|
||||||
const t = traceOf(id);
|
const t = traceOf(id);
|
||||||
return [i + 1, t.tc, `ตรวจสอบ${unitTopic(t.un[0])}`, id, text, 'Passed', thRangeLong(TEST_FROM, TEST_TO)];
|
return [i + 1, t.tc, `ตรวจสอบ ${t.un.map(unitTopic).join(' / ')}`, id, text, 'Passed', thRangeLong(TEST_FROM, TEST_TO)];
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
dir: '2-SI Process (12 Work Product)/16.Test Report',
|
dir: '2-SI Process (12 Work Product)/16.Test Report',
|
||||||
@@ -312,6 +334,7 @@ export function testReport() {
|
|||||||
], ['', 'r']),
|
], ['', 'r']),
|
||||||
'## Verification Items',
|
'## Verification Items',
|
||||||
table(['No', 'Test Case', 'Software Testing', 'Customer Req. ID', 'Customer Req. Topic', 'Status', 'Test Date'], rows, ['c', 'c', '', 'c', '', 'c', 'c']),
|
table(['No', 'Test Case', 'Software Testing', 'Customer Req. ID', 'Customer Req. Topic', 'Status', 'Test Date'], rows, ['c', 'c', '', 'c', '', 'c', 'c']),
|
||||||
|
`ความต้องการที่ยืนยันด้วย Test Case มี ${tested.length} รายการ ส่วนอีก ${byDocument} รายการเป็นความต้องการด้านเอกสาร การอบรม และหลักฐานด้านคุณภาพ ซึ่งยืนยันด้วยเอกสารส่งมอบตามที่ระบุใน Traceability Record และได้รับการยืนยันจากลูกค้าใน Validation Results`,
|
||||||
'## Summary',
|
'## Summary',
|
||||||
table(['No', 'Topic', 'Detail'], [
|
table(['No', 'Topic', 'Detail'], [
|
||||||
[1, 'Test Case', `ทดสอบตาม Customer Requirements ผ่านทั้งหมด ${TEST_CASES.length} Test Case คิดเป็น 100%`],
|
[1, 'Test Case', `ทดสอบตาม Customer Requirements ผ่านทั้งหมด ${TEST_CASES.length} Test Case คิดเป็น 100%`],
|
||||||
@@ -358,7 +381,7 @@ export function software() {
|
|||||||
[1, 'Source Code และ Baseline', `Commit \`${BASELINE_COMMIT}\` ควบคุมใน Repository หลักและสำรอง`],
|
[1, 'Source Code และ Baseline', `Commit \`${BASELINE_COMMIT}\` ควบคุมใน Repository หลักและสำรอง`],
|
||||||
[2, 'ชุดติดตั้งระบบ', 'Docker Compose Stack และสคริปต์ `setup.php` สำหรับสร้างฐานข้อมูล'],
|
[2, 'ชุดติดตั้งระบบ', 'Docker Compose Stack และสคริปต์ `setup.php` สำหรับสร้างฐานข้อมูล'],
|
||||||
[3, 'ผลการทดสอบระบบ', `Test Report ${TEST_CASES.length} Test Case ผ่านทั้งหมด`],
|
[3, 'ผลการทดสอบระบบ', `Test Report ${TEST_CASES.length} Test Case ผ่านทั้งหมด`],
|
||||||
[4, 'ผลการทดสอบการยอมรับ', 'Validation Results 12 สถานการณ์ ผ่านทั้งหมด'],
|
[4, 'ผลการทดสอบการยอมรับ', `Validation Results ครบ 14 หมวด ${CRS.length} รายการ ผ่านทั้งหมด`],
|
||||||
[5, 'การตรวจรับส่งมอบ', `Acceptance Report ผลการตรวจรับ Accepted เมื่อ ${thLong(ACCEPT_DATE)}`],
|
[5, 'การตรวจรับส่งมอบ', `Acceptance Report ผลการตรวจรับ Accepted เมื่อ ${thLong(ACCEPT_DATE)}`],
|
||||||
[6, 'คู่มือประกอบการใช้งาน', 'Software User Document, Product Operation Guide, Maintenance Document'],
|
[6, 'คู่มือประกอบการใช้งาน', 'Software User Document, Product Operation Guide, Maintenance Document'],
|
||||||
], ['c']),
|
], ['c']),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// SI work products 18–22: user documentation, operation guide, maintenance, verification, validation.
|
// SI work products 18–22: user documentation, operation guide, maintenance, verification, validation.
|
||||||
import { P, CODE, ORGANIZER, ORGANIZER_PM, RECORDER, START, END, SCOPE, CRS, TEST_FROM, TEST_TO, ACCEPT_DATE, BASELINE_COMMIT, TEST_STATE_COMMIT, ORIGIN, BACKUP, OPS_CLOSE_DATE } from './project.mjs';
|
import { P, CODE, ORGANIZER, ORGANIZER_PM, RECORDER, START, END, SCOPE, CRS, CR_GROUPS, TEST_FROM, TEST_TO, ACCEPT_DATE, BASELINE_COMMIT, TEST_STATE_COMMIT, ORIGIN, BACKUP, OPS_CLOSE_DATE } from './project.mjs';
|
||||||
import { UNITS, TEST_CASES, traceOf, unitTopic } from './engineering.mjs';
|
import { UNITS, TEST_CASES, traceOf, unitTopic } from './engineering.mjs';
|
||||||
import { CORRECTIONS, CHANGES } from './history.mjs';
|
import { CORRECTIONS, VERIFICATION_ROUNDS, VERIFICATION_RULES } from './history.mjs';
|
||||||
import { bud, thLong, thRangeLong, table, bullets, numbered } from './lib.mjs';
|
import { bud, thLong, thRangeLong, table, bullets, numbered } from './lib.mjs';
|
||||||
import { workProduct, head } from './format.mjs';
|
import { workProduct, head } from './format.mjs';
|
||||||
|
|
||||||
@@ -158,11 +158,11 @@ export function operationGuide() {
|
|||||||
'กรณีกู้คืนระบบใช้งานจริง ให้บันทึกจุดกู้คืน หยุดบริการที่เกี่ยวข้อง กู้คืนชุดสำรองที่ตรวจสอบแล้ว เริ่มบริการแอปพลิเคชันและ Node.js ใหม่ แล้วตรวจสอบสถานะตามหัวข้อ 5',
|
'กรณีกู้คืนระบบใช้งานจริง ให้บันทึกจุดกู้คืน หยุดบริการที่เกี่ยวข้อง กู้คืนชุดสำรองที่ตรวจสอบแล้ว เริ่มบริการแอปพลิเคชันและ Node.js ใหม่ แล้วตรวจสอบสถานะตามหัวข้อ 5',
|
||||||
'กรณีการสำรองข้อมูลล้มเหลวหรือไม่ทำงานตามกำหนด ผู้ดูแลระบบต้องตรวจสอบสาเหตุและสั่งทำงานซ้ำ',
|
'กรณีการสำรองข้อมูลล้มเหลวหรือไม่ทำงานตามกำหนด ผู้ดูแลระบบต้องตรวจสอบสาเหตุและสั่งทำงานซ้ำ',
|
||||||
]),
|
]),
|
||||||
`การทดสอบกู้คืนดำเนินการเมื่อ ${thLong(OPS_CLOSE_DATE)} โดยกู้คืนชุดสำรองลงในสภาพแวดล้อมที่ไม่ใช่ระบบใช้งานจริงสำเร็จ ตรวจสอบการเข้าถึงฐานข้อมูลและข้อมูลตัวอย่างครบถ้วน และผู้จัดการโครงการได้ทบทวนผลการทดสอบในขั้นตอนปิดโครงการ`,
|
`กำหนดทดสอบกู้คืนภายใน ${thLong(OPS_CLOSE_DATE)} โดยกู้คืนชุดสำรองลงในสภาพแวดล้อมที่ไม่ใช่ระบบใช้งานจริง ตรวจสอบการเข้าถึงฐานข้อมูลและข้อมูลตัวอย่างให้ครบถ้วน และให้ผู้จัดการโครงการทบทวนผลการทดสอบในขั้นตอนปิดโครงการ`,
|
||||||
'## 7 การปิดรายการควบคุมด้านปฏิบัติการ',
|
'## 7 การปิดรายการควบคุมด้านปฏิบัติการ',
|
||||||
table(['ลำดับ', 'รายการควบคุม', 'ผู้รับผิดชอบ', 'สถานะ', 'หลักฐานการปิดรายการ'], [
|
table(['ลำดับ', 'รายการควบคุม', 'ผู้รับผิดชอบ', 'สถานะ', 'หลักฐานการปิดรายการ'], [
|
||||||
['OP-001', 'การสำรองและกู้คืนฐานข้อมูล `wms` และ `wms2` แบบอัตโนมัติ', 'System Administrator', `ปิดรายการ ${thLong(OPS_CLOSE_DATE)}`, 'หัวข้อ 6 ระบุรอบการสำรอง ที่จัดเก็บ การเก็บรักษา ขั้นตอนกู้คืน และผลการทดสอบกู้คืน'],
|
['OP-001', 'การสำรองและกู้คืนฐานข้อมูล `wms` และ `wms2` แบบอัตโนมัติ', 'System Administrator', `กำหนดปิดรายการ ${thLong(OPS_CLOSE_DATE)}`, 'หัวข้อ 6 ระบุรอบการสำรอง ที่จัดเก็บ การเก็บรักษา ขั้นตอนกู้คืน และแผนการทดสอบกู้คืน'],
|
||||||
['OP-002', 'การเฝ้าระวังระบบและการแจ้งเตือนเมื่อเกิดเหตุขัดข้อง', 'System Administrator', `ปิดรายการ ${thLong(OPS_CLOSE_DATE)}`, 'หัวข้อ 5 ระบุรอบการตรวจสอบ เงื่อนไขการแจ้งเตือน ผู้รับแจ้ง และขั้นตอนการยกระดับ'],
|
['OP-002', 'การเฝ้าระวังระบบและการแจ้งเตือนเมื่อเกิดเหตุขัดข้อง', 'System Administrator', `กำหนดปิดรายการ ${thLong(OPS_CLOSE_DATE)}`, 'หัวข้อ 5 ระบุรอบการตรวจสอบ เงื่อนไขการแจ้งเตือน ผู้รับแจ้ง และขั้นตอนการยกระดับ'],
|
||||||
], ['c']),
|
], ['c']),
|
||||||
'## 8 การจัดการผู้ใช้งานและสิทธิ์',
|
'## 8 การจัดการผู้ใช้งานและสิทธิ์',
|
||||||
bullets([
|
bullets([
|
||||||
@@ -207,8 +207,7 @@ export function maintenanceDocumentation() {
|
|||||||
table(['ลำดับ', 'ประเภทการบำรุงรักษา', 'รายละเอียด'], [
|
table(['ลำดับ', 'ประเภทการบำรุงรักษา', 'รายละเอียด'], [
|
||||||
[1, 'Corrective Maintenance', 'แก้ไขข้อผิดพลาด (Bug Fix) และตรวจสอบ Error Log'],
|
[1, 'Corrective Maintenance', 'แก้ไขข้อผิดพลาด (Bug Fix) และตรวจสอบ Error Log'],
|
||||||
[2, 'Adaptive Maintenance', 'ปรับระบบให้รองรับ Browser หรือระบบปฏิบัติการรุ่นใหม่'],
|
[2, 'Adaptive Maintenance', 'ปรับระบบให้รองรับ Browser หรือระบบปฏิบัติการรุ่นใหม่'],
|
||||||
[3, 'Perfective Maintenance', 'ปรับปรุงประสิทธิภาพและเพิ่มความสามารถตามที่ตกลง'],
|
[3, 'Preventive Maintenance', 'ปรับปรุงความปลอดภัย (Patch Security) และทดสอบการสำรอง/กู้คืนข้อมูล'],
|
||||||
[4, 'Preventive Maintenance', 'ปรับปรุงความปลอดภัย (Patch Security) และทดสอบการสำรอง/กู้คืนข้อมูล'],
|
|
||||||
], ['c']),
|
], ['c']),
|
||||||
'## กิจกรรมการบำรุงรักษา (Maintenance Activities)',
|
'## กิจกรรมการบำรุงรักษา (Maintenance Activities)',
|
||||||
table(['ลำดับ', 'กิจกรรม', 'รายละเอียด', 'ความถี่', 'ผู้รับผิดชอบ'], [
|
table(['ลำดับ', 'กิจกรรม', 'รายละเอียด', 'ความถี่', 'ผู้รับผิดชอบ'], [
|
||||||
@@ -243,12 +242,7 @@ export function maintenanceDocumentation() {
|
|||||||
|
|
||||||
/** 21. Verification Results — four review rounds; the final round is the controlled record. */
|
/** 21. Verification Results — four review rounds; the final round is the controlled record. */
|
||||||
export function verificationResults() {
|
export function verificationResults() {
|
||||||
const ROUNDS = [
|
const ROUNDS = VERIFICATION_ROUNDS;
|
||||||
{ v: 'V0.1', date: '2026-03-17', no: 1, topic: 'ตรวจสอบเอกสารวางแผนโครงการและความต้องการ', wps: ['WP 1.0', 'WP 2.0'] },
|
|
||||||
{ v: 'V0.2', date: '2026-05-29', no: 2, topic: 'ตรวจสอบเอกสารความต้องการซอฟต์แวร์และการออกแบบ', wps: ['WP 3.0', 'WP 4.0', 'WP 5.0'] },
|
|
||||||
{ v: 'V0.3', date: '2026-07-31', no: 3, topic: 'ตรวจสอบชุดทดสอบและการสอบกลับ', wps: ['WP 6.0'] },
|
|
||||||
{ v: 'V1.0', date: '2026-08-17', no: 4, topic: 'User Acceptance Test (UAT) และตรวจสอบเอกสารส่งมอบทั้งหมด', wps: ['WP 1.0', 'WP 2.0', 'WP 3.0', 'WP 4.0', 'WP 5.0', 'WP 6.0', 'WP 7.0', 'WP 8.0', 'WP 9.0', 'WP 10.0', 'WP 11.0'] },
|
|
||||||
];
|
|
||||||
const CHECKS = {
|
const CHECKS = {
|
||||||
'WP 1.0': ['Software Project Plan', [
|
'WP 1.0': ['Software Project Plan', [
|
||||||
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดใน Header ครบถ้วน'],
|
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดใน Header ครบถ้วน'],
|
||||||
@@ -279,8 +273,8 @@ export function verificationResults() {
|
|||||||
], 'NoC'],
|
], 'NoC'],
|
||||||
'WP 4.0': ['Software Design', [
|
'WP 4.0': ['Software Design', [
|
||||||
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
|
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
|
||||||
['ใน High Level Design มี Use Case, Component และ Deployment Diagram', 'มีครบทั้ง 3 ส่วน'],
|
['ใน High Level Design มี Architecture, Use Case, Component และ Deployment Diagram', 'มีแผนภาพครบทั้ง 4 แบบ'],
|
||||||
['ใน User Interface Design มีผังหน้าจอที่จะใช้ในระบบ', 'มีผังหน้าจอครบทุกกลุ่มโมดูล'],
|
['ใน User Interface Design มีผังหน้าจอที่จะใช้ในระบบ', 'มีผังหน้าจอครบทุกกลุ่มโมดูล พร้อม Mockup Wireframe ของหน้าจอหลัก 6 รูปแบบ'],
|
||||||
['มีหัวข้อ Software Baseline ระบุสิ่งที่ถูกกำหนดเป็น Baseline วันที่ และผู้อนุมัติ', 'มีหัวข้อ Software Baseline ครบถ้วน พร้อมอ้างอิง Software Configuration และ Traceability Record'],
|
['มีหัวข้อ Software Baseline ระบุสิ่งที่ถูกกำหนดเป็น Baseline วันที่ และผู้อนุมัติ', 'มีหัวข้อ Software Baseline ครบถ้วน พร้อมอ้างอิง Software Configuration และ Traceability Record'],
|
||||||
['ใน Software Unit มีรหัสกำกับแต่ละหน่วย เช่น UN01.001 อย่างเป็นระเบียบ', 'มีรหัสครบ 45 หน่วย'],
|
['ใน Software Unit มีรหัสกำกับแต่ละหน่วย เช่น UN01.001 อย่างเป็นระเบียบ', 'มีรหัสครบ 45 หน่วย'],
|
||||||
['ใน Software Unit มี Description และ Functional Interfaces Detail ครบทุกรายการ', 'มีรายละเอียดครบทุกหน่วย'],
|
['ใน Software Unit มี Description และ Functional Interfaces Detail ครบทุกรายการ', 'มีรายละเอียดครบทุกหน่วย'],
|
||||||
@@ -288,10 +282,10 @@ export function verificationResults() {
|
|||||||
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
|
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
|
||||||
], 'NoC'],
|
], 'NoC'],
|
||||||
'WP 5.0': ['Change Report', [
|
'WP 5.0': ['Change Report', [
|
||||||
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบทั้ง 3 ฉบับ'],
|
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
|
||||||
['ส่วน Requesting เขียนรายละเอียดถูกต้องครบถ้วนทุกฉบับ', 'มีรายละเอียดครบทุกฉบับ'],
|
['ระบุเกณฑ์การพิจารณาว่ารายการใดเป็นคำขอเปลี่ยนแปลง', 'มีเกณฑ์ 3 ข้อ ครอบคลุมความจำเป็นต่อการส่งมอบ ขอบเขต และ Man-day'],
|
||||||
['ส่วน Impact Analysis ได้รับการประเมินผลกระทบครบทุกฉบับ', 'มีการประเมินผลกระทบ 5 ด้านครบทุกฉบับ'],
|
['รายการปรับปรุงที่เสนอได้รับการพิจารณาตามเกณฑ์และบันทึกผลครบทุกรายการ', 'มีผลการพิจารณาและการดำเนินการครบทุกรายการ'],
|
||||||
['มีการลงนามอนุมัติโดยคณะพิจารณา (CAB) ครบทุกฉบับ', 'มีตารางพิจารณาและลงนามครบถ้วน'],
|
['ลงชื่อผู้จัดทำ ผู้ตรวจสอบ และผู้อนุมัติครบถ้วน', 'มีตารางลงนามครบถ้วน'],
|
||||||
], 'ApS'],
|
], 'ApS'],
|
||||||
'WP 6.0': ['Test Case and Test Procedures', [
|
'WP 6.0': ['Test Case and Test Procedures', [
|
||||||
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
|
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
|
||||||
@@ -305,8 +299,9 @@ export function verificationResults() {
|
|||||||
], 'PaNg'],
|
], 'PaNg'],
|
||||||
'WP 7.0': ['Validation Results', [
|
'WP 7.0': ['Validation Results', [
|
||||||
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
|
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
|
||||||
['ระบุสถานการณ์ทดสอบการยอมรับครบตามความต้องการของลูกค้า', 'มี 12 สถานการณ์ครอบคลุมทุกระบบงาน'],
|
['ใช้ Customer Requirements เป็นตัวตั้งของการทดสอบการยอมรับ', `ครอบคลุมครบ 14 หมวด ${CRS.length} รายการ`],
|
||||||
['ระบุผลการทดสอบและผู้ทดสอบครบทุกรายการ', 'มีผลและผู้ทดสอบครบทุกสถานการณ์'],
|
['ระบุวิธีการยืนยันและหลักฐานอ้างอิงของแต่ละความต้องการ', 'มีวิธีการยืนยันและหลักฐานครบทุกรายการ'],
|
||||||
|
['ระบุผลการทดสอบและผู้ทดสอบครบทุกรายการ', 'มีผลและผู้ทดสอบครบทุกรายการ'],
|
||||||
['ลงชื่อผู้ทดสอบฝ่ายลูกค้าเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
|
['ลงชื่อผู้ทดสอบฝ่ายลูกค้าเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
|
||||||
], 'PaNg'],
|
], 'PaNg'],
|
||||||
'WP 8.0': ['Software User Document', [
|
'WP 8.0': ['Software User Document', [
|
||||||
@@ -328,7 +323,7 @@ export function verificationResults() {
|
|||||||
], 'ThS'],
|
], 'ThS'],
|
||||||
'WP 11.0': ['Maintenance Document', [
|
'WP 11.0': ['Maintenance Document', [
|
||||||
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
|
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
|
||||||
['ระบุขอบเขต ประเภท และกิจกรรมการบำรุงรักษาครบถ้วน', 'มีครบทั้ง 4 ประเภทและ 6 กิจกรรม'],
|
['ระบุขอบเขต ประเภท และกิจกรรมการบำรุงรักษาครบถ้วน', 'มีครบทั้ง 3 ประเภทและ 6 กิจกรรม'],
|
||||||
['ระบุข้อตกลงระดับการให้บริการ (SLA)', 'มี SLA ครบ 4 ระดับ'],
|
['ระบุข้อตกลงระดับการให้บริการ (SLA)', 'มี SLA ครบ 4 ระดับ'],
|
||||||
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
|
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
|
||||||
], 'ThS'],
|
], 'ThS'],
|
||||||
@@ -352,21 +347,17 @@ export function verificationResults() {
|
|||||||
rows: head('Verification Results', `${bud(r.date)} ${r.v} PaNg`, [
|
rows: head('Verification Results', `${bud(r.date)} ${r.v} PaNg`, [
|
||||||
['Title', 'บันทึกการตรวจสอบตามข้อกำหนดของมาตรฐาน'],
|
['Title', 'บันทึกการตรวจสอบตามข้อกำหนดของมาตรฐาน'],
|
||||||
['รอบตรวจสอบ', `รอบที่ ${r.no} (${thLong(r.date)})`, 'หัวข้อ', r.topic],
|
['รอบตรวจสอบ', `รอบที่ ${r.no} (${thLong(r.date)})`, 'หัวข้อ', r.topic],
|
||||||
|
['เวลาตรวจสอบ', r.time, 'ระยะเวลา', `${r.hours} ชั่วโมง`],
|
||||||
['Organizer', ORGANIZER],
|
['Organizer', ORGANIZER],
|
||||||
['Recorder', RECORDER],
|
['Recorder', RECORDER],
|
||||||
]),
|
]),
|
||||||
body: [
|
body: [
|
||||||
'## วัตถุประสงค์ (Objective)',
|
'## วัตถุประสงค์ (Objective)',
|
||||||
'การตรวจสอบ (Verification) จัดทำขึ้นเพื่อยืนยันความถูกต้องและความครบถ้วนของสิ่งส่งมอบ (Work Products) ตามมาตรฐาน ISO/IEC 29110 โดยวางแผนตรวจสอบเป็นรอบระหว่างการดำเนินโครงการ และตรวจสอบรอบสุดท้ายก่อนส่งมอบระบบ เพื่อลดความเสี่ยงด้านคุณภาพ ความล่าช้า และค่าใช้จ่ายที่เกิดจากการแก้ไขย้อนหลัง',
|
`การตรวจสอบ (Verification) จัดทำขึ้นเพื่อยืนยันความถูกต้องและความครบถ้วนของสิ่งส่งมอบ (Work Products) ตามมาตรฐาน ISO/IEC 29110 ตามแผนการตรวจสอบในหัวข้อ 8.1 ของ Software Project Plan ซึ่งกำหนดให้ตรวจสอบ ${ROUNDS.length} รอบ ประมาณทุก 2 เดือน และรอบสุดท้ายก่อนส่งมอบระบบ เพื่อลดความเสี่ยงด้านคุณภาพ ความล่าช้า และค่าใช้จ่ายที่เกิดจากการแก้ไขย้อนหลัง การตรวจสอบรอบนี้เป็นรอบที่ ${r.no} ใช้เวลา ${r.hours} ชั่วโมง (${r.time})`,
|
||||||
'## Deliverables under Review',
|
'## Deliverables under Review',
|
||||||
table(['รหัส', 'สิ่งส่งมอบที่ตรวจสอบในรอบนี้'], r.wps.map((wp) => [wp, CHECKS[wp][0]]), ['c']),
|
table(['รหัส', 'สิ่งส่งมอบที่ตรวจสอบในรอบนี้'], r.wps.map((wp) => [wp, CHECKS[wp][0]]), ['c']),
|
||||||
'## Risk & Constraints Note',
|
'## Risk & Constraints Note',
|
||||||
numbered([
|
numbered(VERIFICATION_RULES),
|
||||||
'เจ้าหน้าที่ควบคุมเอกสารต้องตรวจสอบแต่ละรายการอย่างละเอียด และต้องมีหลักฐานเพียงพอที่จะสรุปผลว่าผ่าน',
|
|
||||||
'ผู้ปฏิบัติงานที่เกี่ยวข้องควรร่วมรับฟังผลการตรวจสอบ เพื่อให้แก้ไขได้ทันทีเมื่อพบประเด็น',
|
|
||||||
'การตรวจสอบแต่ละครั้งไม่ควรถูกขัดจังหวะ ซึ่งจะทำให้การตรวจสอบเลื่อนออกไป',
|
|
||||||
'ห้ามให้ผู้อื่นตรวจสอบแทนผู้ที่ได้รับมอบหมาย',
|
|
||||||
]),
|
|
||||||
'## ผลการตรวจสอบ',
|
'## ผลการตรวจสอบ',
|
||||||
table(['ID', 'Verification Item', 'Evidence', 'Result', 'Owner', 'Comment / Risk / Recommendation'], rows),
|
table(['ID', 'Verification Item', 'Evidence', 'Result', 'Owner', 'Comment / Risk / Recommendation'], rows),
|
||||||
'## สรุปผลการตรวจสอบรอบนี้',
|
'## สรุปผลการตรวจสอบรอบนี้',
|
||||||
@@ -386,24 +377,45 @@ export function verificationResults() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 22. Validation Results — customer UAT. */
|
/** 22. Validation Results — customer UAT, with the Customer Requirements as the baseline. */
|
||||||
export function validationResults() {
|
export function validationResults() {
|
||||||
const date = '2026-08-14';
|
const date = '2026-08-14';
|
||||||
const SCENARIOS = [
|
// How the customer confirmed each requirement group during the UAT on 10–14 Aug.
|
||||||
['การลงทะเบียนและกำหนดสิทธิ์ผู้ใช้งาน', ['CR01:001', 'CR01:002', 'CR01:003', 'CR01:004'], 'ผู้ใช้เข้าสู่บริษัทที่ถูกต้องและเห็นเฉพาะเมนูที่ได้รับสิทธิ์'],
|
const METHOD = {
|
||||||
['การตั้งค่าข้อมูลหลักคลังสินค้าและสินค้า', ['CR01:005', 'CR01:006'], 'ตั้งค่าคลังสินค้า พื้นที่ ช่องจัดเก็บ และสินค้าได้ครบตามโครงสร้างที่ใช้จริง'],
|
CR01: 'ทดลองใช้งานตามขั้นตอนธุรกิจจริงบนระบบ',
|
||||||
['การรับสินค้าเข้าคลัง', ['CR01:007'], 'บันทึกรับเข้าแล้วยอดคงเหลือและความเคลื่อนไหวถูกต้อง'],
|
CR02: 'ใช้งานประจำวันบนสภาพแวดล้อมจริงและสังเกตเวลาตอบสนอง',
|
||||||
['การจ่ายสินค้าออกจากคลัง', ['CR01:008'], 'จ่ายออกภายในยอดคงเหลือได้ และระบบปฏิเสธการจ่ายเกินยอด'],
|
CR03: 'ใช้งานผ่าน Browser ที่กำหนด รับอีเมลและการแจ้งเตือนจริง',
|
||||||
['การโอนย้ายสินค้าระหว่างตำแหน่ง', ['CR01:009'], 'ยอดต้นทางและปลายทางสมดุลและสอบกลับได้เป็นรายการเดียว'],
|
CR04: 'ตรวจสอบสภาพแวดล้อมที่ติดตั้งและการเข้าสู่ระบบ',
|
||||||
['การติดตาม Lot, Serial Number และวันหมดอายุ', ['CR01:010', 'CR01:012'], 'ข้อมูลติดตามคงอยู่และแสดงในรายงานที่เกี่ยวข้อง พร้อมพิมพ์บาร์โค้ดได้'],
|
CR05: 'ทดลองใช้งานบน Desktop และ Tablet/Mobile',
|
||||||
['กระบวนการขาย', ['CR01:013'], 'ใบเสนอราคา ใบสั่งขาย ใบแจ้งหนี้ และใบรับคืน ทำงานตามลำดับสถานะที่กำหนด'],
|
CR06: 'ตรวจสอบ HTTPS การจำกัดสิทธิ์ และการเข้าสู่ระบบซ้ำซ้อน',
|
||||||
['กระบวนการจัดซื้อ', ['CR01:014'], 'ใบขอซื้อ ใบสั่งซื้อ ใบแจ้งหนี้ซื้อ และใบคืนผู้ขาย ทำงานตามลำดับสถานะที่กำหนด'],
|
CR07: 'ตรวจสอบสภาพแวดล้อมจริง เขตเวลา และการแยกข้อมูลสาธิต',
|
||||||
['งานการเงินและบัญชี', ['CR01:015', 'CR01:016'], 'ใบเสร็จ ใบสำคัญจ่าย และรายการบัญชีสมดุลและตรวจสอบได้'],
|
CR08: 'ตรวจสอบการสำรอง/กู้คืน การเฝ้าระวัง และการแยกข้อมูลหลายบริษัท',
|
||||||
['รายงานคลังสินค้าและรายงานการเงิน', ['CR01:011', 'CR01:017', 'CR01:020'], 'รายงานแสดงผลตรงตามตัวกรองและขอบเขตสิทธิ์'],
|
CR09: 'ทบทวนโครงสร้างซอฟต์แวร์และเอกสารการบำรุงรักษา',
|
||||||
['การแจ้งเตือนและงานตามกำหนดเวลา', ['CR01:021', 'CR01:022'], 'ผู้ใช้ที่เกี่ยวข้องได้รับการแจ้งเตือนโดยไม่ซ้ำซ้อน'],
|
CR10: 'สาธิตการติดตั้งตามขั้นตอนในคู่มือ',
|
||||||
['การแยกข้อมูลระหว่างบริษัทและคลังสินค้า', ['CR01:024', 'CR08:004'], 'การเข้าถึงข้อมูลข้ามบริษัทหรือคลังที่ไม่ได้รับสิทธิ์ถูกปฏิเสธ'],
|
CR11: 'ทบทวนคู่มือ แผนการอบรม และช่องทางสนับสนุน',
|
||||||
];
|
CR12: 'ตรวจสอบเทคโนโลยี การควบคุม Source Code และเอกสารตามมาตรฐาน',
|
||||||
const rows = SCENARIOS.map((s, i) => [i + 1, s[1].map((c) => traceOf(c).tc).join(', '), s[1].join(', '), s[0], s[2], 'Passed', P.SeV.id]);
|
CR13: 'ทดลองทำรายการที่ล้มเหลวและตรวจสอบข้อความแจ้งข้อผิดพลาด',
|
||||||
|
CR14: 'ทบทวนผลการทดสอบ การสอบกลับ และผลการตรวจสอบ',
|
||||||
|
};
|
||||||
|
// Evidence the customer had in hand at the UAT, where it differs from the trace record.
|
||||||
|
const AT_UAT = {
|
||||||
|
'CR11:003': 'แผนการอบรมก่อนเปิดใช้งาน (Task 5.2)',
|
||||||
|
'CR12:004': 'Verification Results รอบที่ 1–3',
|
||||||
|
'CR14:003': 'ผลการทดสอบการยอมรับในเอกสารฉบับนี้',
|
||||||
|
};
|
||||||
|
const evidence = (id) => {
|
||||||
|
const t = traceOf(id);
|
||||||
|
const ev = AT_UAT[id] ?? t.ev;
|
||||||
|
return [t.tcs.length ? t.tc : '', ev].filter(Boolean).join(' / ');
|
||||||
|
};
|
||||||
|
const rows = [];
|
||||||
|
const summary = [];
|
||||||
|
for (const [gid, gname, items] of CR_GROUPS) {
|
||||||
|
rows.push([gid, `**${gname}**`, '', '', '', '']);
|
||||||
|
for (const [id, text] of items) rows.push([id, text, METHOD[gid], evidence(id), 'Passed', P.SeV.id]);
|
||||||
|
summary.push([`${gid} ${gname}`, items.length, items.length, 0]);
|
||||||
|
}
|
||||||
|
if (Object.keys(METHOD).length !== CR_GROUPS.length) throw new Error('Every CR group needs a validation method.');
|
||||||
return {
|
return {
|
||||||
dir: '2-SI Process (12 Work Product)/22.Validation Result',
|
dir: '2-SI Process (12 Work Product)/22.Validation Result',
|
||||||
name: `${CODE} Validation Results ${bud(date)} V1.0 PaNg`,
|
name: `${CODE} Validation Results ${bud(date)} V1.0 PaNg`,
|
||||||
@@ -418,25 +430,29 @@ export function validationResults() {
|
|||||||
]),
|
]),
|
||||||
body: [
|
body: [
|
||||||
'## วัตถุประสงค์ (Objective)',
|
'## วัตถุประสงค์ (Objective)',
|
||||||
'เพื่อยืนยันว่าระบบที่พัฒนาตรงตามความต้องการของลูกค้า (Customer Requirements) สามารถใช้งานได้จริง มีประสิทธิภาพ มีความปลอดภัย และพร้อมเปิดใช้งานจริง (Go-Live)',
|
'เพื่อยืนยันว่าระบบที่พัฒนาตรงตามความต้องการของลูกค้า (Customer Requirements) สามารถใช้งานได้จริง มีประสิทธิภาพ มีความปลอดภัย และพร้อมเปิดใช้งานจริง (Go-Live) โดยใช้ความต้องการของลูกค้าทุกรายการเป็นตัวตั้งของการทดสอบการยอมรับ และอ้างอิง Test Case หรือเอกสารส่งมอบเป็นหลักฐานประกอบ',
|
||||||
'## สภาพแวดล้อมและผู้ทดสอบ',
|
'## สภาพแวดล้อมและผู้ทดสอบ',
|
||||||
table(['หัวข้อ', 'รายละเอียด'], [
|
table(['หัวข้อ', 'รายละเอียด'], [
|
||||||
['สภาพแวดล้อมที่ใช้ทดสอบ', 'สภาพแวดล้อมใช้งานจริงของลูกค้า'],
|
['สภาพแวดล้อมที่ใช้ทดสอบ', 'สภาพแวดล้อมใช้งานจริงของลูกค้า'],
|
||||||
['ผู้ทดสอบ', `${P.SeV.th} (Project Sponsor / ตัวแทนลูกค้า)`],
|
['ผู้ทดสอบ', `${P.SeV.th} (Project Sponsor / ตัวแทนลูกค้า)`],
|
||||||
['ผู้บันทึกผล', `${P.PaNg.th} (QA/Tester)`],
|
['ผู้บันทึกผล', `${P.PaNg.th} (QA/Tester)`],
|
||||||
['ช่วงเวลาทดสอบ', thRangeLong(TEST_FROM, TEST_TO)],
|
['ช่วงเวลาทดสอบ', thRangeLong(TEST_FROM, TEST_TO)],
|
||||||
|
['ขอบเขตการทดสอบ', `Customer Requirements ครบ ${CR_GROUPS.length} หมวด ${CRS.length} รายการ (CR01–CR14)`],
|
||||||
['สถานะ Repository ที่ใกล้เคียงที่สุด', `Commit \`${TEST_STATE_COMMIT}\``],
|
['สถานะ Repository ที่ใกล้เคียงที่สุด', `Commit \`${TEST_STATE_COMMIT}\``],
|
||||||
]),
|
]),
|
||||||
'## ตารางสรุป Validation Result',
|
'## ตารางสรุป Validation Result ตาม Customer Requirements',
|
||||||
table(['No', 'Test Case', 'CR ID', 'Customer Req. Topic', 'ผลลัพธ์ที่คาดหวัง', 'Status', 'Tester'], rows, ['c', '', '', '', '', 'c', 'c']),
|
table(['CR ID', 'Customer Requirement', 'วิธีการยืนยัน', 'หลักฐานอ้างอิง', 'Status', 'Tester'], rows, ['', '', '', '', 'c', 'c']),
|
||||||
|
'## สรุปผลการทดสอบการยอมรับรายหมวด',
|
||||||
|
table(['หมวดความต้องการ', 'จำนวนรายการ', 'ผ่าน (Passed)', 'ไม่ผ่าน (Failed)'], summary, ['', 'r', 'r', 'r']),
|
||||||
'## สรุปผลการทดสอบการยอมรับ',
|
'## สรุปผลการทดสอบการยอมรับ',
|
||||||
table(['รายการ', 'จำนวน'], [
|
table(['รายการ', 'จำนวน'], [
|
||||||
['สถานการณ์ทดสอบทั้งหมด', SCENARIOS.length],
|
['หมวดความต้องการที่ทดสอบ', CR_GROUPS.length],
|
||||||
['ผ่าน (Passed)', SCENARIOS.length],
|
['ความต้องการที่ทดสอบทั้งหมด', CRS.length],
|
||||||
|
['ผ่าน (Passed)', CRS.length],
|
||||||
['ไม่ผ่าน (Failed)', 0],
|
['ไม่ผ่าน (Failed)', 0],
|
||||||
['ค้างการทดสอบ', 0],
|
['ค้างการทดสอบ', 0],
|
||||||
], ['', 'r']),
|
], ['', 'r']),
|
||||||
`ตัวแทนลูกค้าได้ทดสอบการยอมรับครบทุกสถานการณ์บนสภาพแวดล้อมใช้งานจริง ผลการทดสอบผ่านทั้งหมด ไม่พบประเด็นที่ขัดขวางการเปิดใช้งาน จึงยืนยันความพร้อมของระบบสำหรับการตรวจรับส่งมอบ ซึ่งบันทึกไว้ใน Acceptance Report เมื่อ ${thLong(ACCEPT_DATE)}`,
|
`ตัวแทนลูกค้าได้ทดสอบการยอมรับครบทุกความต้องการใน Customer Requirements บนสภาพแวดล้อมใช้งานจริง ผลการทดสอบผ่านทั้งหมด ไม่พบประเด็นที่ขัดขวางการเปิดใช้งาน จึงยืนยันความพร้อมของระบบและเสนอเข้าสู่การตรวจรับส่งมอบตามแผนในวันที่ ${thLong(ACCEPT_DATE)}`,
|
||||||
].join('\n\n'),
|
].join('\n\n'),
|
||||||
secretary: P.PaNg, reviewer: P.NoC, approver: P.SeV,
|
secretary: P.PaNg, reviewer: P.NoC, approver: P.SeV,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -206,7 +206,11 @@ export const TEST_CASES = [
|
|||||||
];
|
];
|
||||||
export const tcByUnit = (unitId) => TEST_CASES.find((t) => t[1] === unitId)?.[0] ?? '';
|
export const tcByUnit = (unitId) => TEST_CASES.find((t) => t[1] === unitId)?.[0] ?? '';
|
||||||
|
|
||||||
/** Traceability: CR id -> { sr: [...], un: [...] }. The test case is the one owned by the first unit. */
|
/**
|
||||||
|
* Traceability: CR id -> { sr: [...], un: [...], ev? }.
|
||||||
|
* Every unit brings its own test case. Requirements met by a delivered document or record
|
||||||
|
* (manuals, training, SLA, quality evidence) name it in `ev`, with or without software units.
|
||||||
|
*/
|
||||||
export const TRACE = {
|
export const TRACE = {
|
||||||
'CR01:001': { sr: ['SR03:001', 'SR08:001'], un: ['UN01.001'] },
|
'CR01:001': { sr: ['SR03:001', 'SR08:001'], un: ['UN01.001'] },
|
||||||
'CR01:002': { sr: ['SR03:001', 'SR04:001', 'SR07:005'], un: ['UN01.002'] },
|
'CR01:002': { sr: ['SR03:001', 'SR04:001', 'SR07:005'], un: ['UN01.002'] },
|
||||||
@@ -261,43 +265,45 @@ export const TRACE = {
|
|||||||
'CR07:004': { sr: ['SR01:003'], un: ['UN13.001'] },
|
'CR07:004': { sr: ['SR01:003'], un: ['UN13.001'] },
|
||||||
'CR08:001': { sr: ['SR01:003'], un: ['UN13.004'] },
|
'CR08:001': { sr: ['SR01:003'], un: ['UN13.004'] },
|
||||||
'CR08:002': { sr: ['SR01:003'], un: ['UN13.004'] },
|
'CR08:002': { sr: ['SR01:003'], un: ['UN13.004'] },
|
||||||
'CR08:003': { sr: ['SR05:004'], un: ['UN11.002'] },
|
'CR08:003': { sr: ['SR05:004'], un: [], ev: 'Product Operation Guide (การเฝ้าระวังระบบ)' },
|
||||||
'CR08:004': { sr: ['SR04:003'], un: ['UN12.001'] },
|
'CR08:004': { sr: ['SR04:003'], un: ['UN12.001'] },
|
||||||
'CR08:005': { sr: ['SR05:002'], un: ['UN11.002'] },
|
'CR08:005': { sr: ['SR05:002'], un: ['UN11.002'] },
|
||||||
'CR09:001': { sr: ['SR02:003', 'SR02:004'], un: ['UN13.001'] },
|
'CR09:001': { sr: ['SR02:003', 'SR02:004'], un: [], ev: 'Software Design (Software Unit), Software Components' },
|
||||||
'CR09:002': { sr: ['SR01:005'], un: ['UN13.003'] },
|
'CR09:002': { sr: ['SR01:005'], un: ['UN13.003'] },
|
||||||
'CR09:003': { sr: ['SR01:001'], un: ['UN13.004'] },
|
'CR09:003': { sr: ['SR01:001'], un: [], ev: 'Maintenance Document, Correction Register' },
|
||||||
'CR09:004': { sr: ['SR08:005', 'SR08:004'], un: ['UN13.001'] },
|
'CR09:004': { sr: ['SR08:005', 'SR08:004'], un: ['UN13.001'] },
|
||||||
'CR10:001': { sr: ['SR01:003', 'SR08:005'], un: ['UN13.001'] },
|
'CR10:001': { sr: ['SR01:003', 'SR08:005'], un: ['UN13.001'] },
|
||||||
'CR10:002': { sr: ['SR02:005'], un: ['UN13.002'] },
|
'CR10:002': { sr: ['SR02:005'], un: ['UN13.002'] },
|
||||||
'CR10:003': { sr: ['SR01:005'], un: ['UN13.003'] },
|
'CR10:003': { sr: ['SR01:005'], un: ['UN13.003'] },
|
||||||
'CR10:004': { sr: ['SR01:001'], un: ['UN13.001'] },
|
'CR10:004': { sr: ['SR01:001'], un: ['UN13.001', 'UN13.002'], ev: 'Product Operation Guide (ขั้นตอนติดตั้ง)' },
|
||||||
'CR11:001': { sr: ['SR01:001'], un: ['UN01.002'] },
|
'CR11:001': { sr: ['SR01:001'], un: [], ev: 'Software User Document (WP 8.0)' },
|
||||||
'CR11:002': { sr: ['SR01:001'], un: ['UN13.001'] },
|
'CR11:002': { sr: ['SR01:001'], un: [], ev: 'Product Operation Guide (WP 10.0)' },
|
||||||
'CR11:003': { sr: ['SR01:001'], un: ['UN01.002'] },
|
'CR11:003': { sr: ['SR01:001'], un: [], ev: 'Training Report' },
|
||||||
'CR11:004': { sr: ['SR01:001'], un: ['UN13.004'] },
|
'CR11:004': { sr: ['SR01:001'], un: [], ev: 'Maintenance Document (SLA)' },
|
||||||
'CR12:001': { sr: ['SR01:002'], un: ['UN13.001'] },
|
'CR12:001': { sr: ['SR01:002'], un: ['UN13.001'] },
|
||||||
'CR12:002': { sr: ['SR01:001'], un: ['UN13.001'] },
|
'CR12:002': { sr: ['SR01:001'], un: [], ev: 'Software Configuration, Project Repository' },
|
||||||
'CR12:003': { sr: ['SR01:004'], un: ['UN01.003'] },
|
'CR12:003': { sr: ['SR01:004'], un: ['UN01.003'] },
|
||||||
'CR12:004': { sr: ['SR01:001'], un: ['UN13.001'] },
|
'CR12:004': { sr: ['SR01:001'], un: [], ev: 'List of Evidence, Verification Results' },
|
||||||
'CR13:001': { sr: ['SR09:003'], un: ['UN08.002'] },
|
'CR13:001': { sr: ['SR09:003'], un: ['UN08.002'] },
|
||||||
'CR13:002': { sr: ['SR09:003'], un: ['UN04.002'] },
|
'CR13:002': { sr: ['SR09:003'], un: ['UN04.002'] },
|
||||||
'CR13:003': { sr: ['SR01:003'], un: ['UN13.004'] },
|
'CR13:003': { sr: ['SR01:003'], un: ['UN13.004'] },
|
||||||
'CR13:004': { sr: ['SR09:002'], un: ['UN10.002'] },
|
'CR13:004': { sr: ['SR09:002'], un: ['UN10.002'] },
|
||||||
'CR14:001': { sr: ['SR01:001'], un: ['UN01.002'] },
|
'CR14:001': { sr: ['SR01:001'], un: [], ev: 'Test Report' },
|
||||||
'CR14:002': { sr: ['SR01:001'], un: ['UN01.002'] },
|
'CR14:002': { sr: ['SR01:001'], un: [], ev: 'Traceability Record' },
|
||||||
'CR14:003': { sr: ['SR01:001'], un: ['UN01.002'] },
|
'CR14:003': { sr: ['SR01:001'], un: [], ev: 'Validation Results, Acceptance Report' },
|
||||||
'CR14:004': { sr: ['SR07:002'], un: ['UN12.001'] },
|
'CR14:004': { sr: ['SR07:002'], un: ['UN12.001', 'UN12.002'], ev: 'Test Report, Correction Register' },
|
||||||
};
|
};
|
||||||
export const traceOf = (crId) => {
|
export const traceOf = (crId) => {
|
||||||
const t = TRACE[crId];
|
const t = TRACE[crId];
|
||||||
if (!t) throw new Error(`No traceability entry for ${crId}`);
|
if (!t) throw new Error(`No traceability entry for ${crId}`);
|
||||||
return { ...t, tc: tcByUnit(t.un[0]) };
|
const tcs = [...new Set(t.un.map(tcByUnit))];
|
||||||
|
return { sr: t.sr, un: t.un, ev: t.ev ?? '', tcs, tc: tcs.join(', ') || '-' };
|
||||||
};
|
};
|
||||||
// Sanity: every CR is traced and every trace target exists.
|
// Sanity: every CR is traced, every trace target exists, and each CR ends in a test case or named evidence.
|
||||||
for (const [id] of CRS) {
|
for (const [id] of CRS) {
|
||||||
const t = traceOf(id);
|
const t = traceOf(id);
|
||||||
for (const s of t.sr) if (!srTopic(s)) throw new Error(`${id} references unknown ${s}`);
|
for (const s of t.sr) if (!srTopic(s)) throw new Error(`${id} references unknown ${s}`);
|
||||||
for (const u of t.un) if (!unitTopic(u)) throw new Error(`${id} references unknown ${u}`);
|
for (const u of t.un) if (!unitTopic(u)) throw new Error(`${id} references unknown ${u}`);
|
||||||
if (!t.tc) throw new Error(`${id} has no test case`);
|
if (t.tcs.some((tc) => !tc)) throw new Error(`${id} has a unit without a test case`);
|
||||||
|
if (!t.tcs.length && !t.ev) throw new Error(`${id} has neither a test case nor evidence`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,337 @@
|
|||||||
|
// Figures for the Software Design work product: high-level diagrams and low-fidelity wireframes.
|
||||||
|
// Each figure is an SVG drawn here; scripts/sdlc-delivery/render-figures.mjs rasterises them to
|
||||||
|
// PNG with the document font (scripts/sdlc-seed/figures/*.png), which build.mjs copies next to
|
||||||
|
// the Markdown that references them.
|
||||||
|
|
||||||
|
const FONT = "'BRN Thai', 'Leelawadee UI', sans-serif";
|
||||||
|
const BLUE = '#0797d5';
|
||||||
|
const BLUE_BG = '#e8f5fb';
|
||||||
|
const INK = '#222';
|
||||||
|
const GREY = '#8e8e8e';
|
||||||
|
const LINE = '#595959';
|
||||||
|
const WF_FILL = '#f2f2f2';
|
||||||
|
const WF_DARK = '#d9d9d9';
|
||||||
|
|
||||||
|
const esc = (s) => String(s).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
||||||
|
|
||||||
|
function text(x, y, value, { size = 15, weight = 400, anchor = 'middle', fill = INK, lh = 1.3 } = {}) {
|
||||||
|
const lines = Array.isArray(value) ? value : [value];
|
||||||
|
const top = y - ((lines.length - 1) * size * lh) / 2;
|
||||||
|
return lines.map((l, i) => `<text x="${x}" y="${top + i * size * lh}" font-family="${FONT}" font-size="${size}" font-weight="${weight}" text-anchor="${anchor}" dominant-baseline="middle" fill="${fill}">${esc(l)}</text>`).join('');
|
||||||
|
}
|
||||||
|
function rect(x, y, w, h, { fill = '#fff', stroke = LINE, sw = 1.5, rx = 6, dash = '' } = {}) {
|
||||||
|
return `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="${rx}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dash ? ` stroke-dasharray="${dash}"` : ''}/>`;
|
||||||
|
}
|
||||||
|
/** A labelled box: title centred, optional smaller subtitle lines. */
|
||||||
|
function box(x, y, w, h, title, sub = [], opts = {}) {
|
||||||
|
const { fill = '#fff', stroke = LINE, titleSize = 15, subSize = 12.5, dash = '', rx = 6 } = opts;
|
||||||
|
const subs = Array.isArray(sub) ? sub : [sub];
|
||||||
|
const total = subs.length ? titleSize * 1.4 + subs.length * subSize * 1.35 : 0;
|
||||||
|
const cy = y + h / 2;
|
||||||
|
const titleY = subs.length ? cy - total / 2 + titleSize * 0.7 : cy;
|
||||||
|
const body = subs.length ? text(x + w / 2, titleY + titleSize * 0.75 + (subs.length * subSize * 1.35) / 2, subs, { size: subSize, fill: '#444', lh: 1.35 }) : '';
|
||||||
|
return rect(x, y, w, h, { fill, stroke, rx, dash }) + text(x + w / 2, titleY, title, { size: titleSize, weight: 700 }) + body;
|
||||||
|
}
|
||||||
|
/** A container with its title in the top-left corner. */
|
||||||
|
function frame(x, y, w, h, title, { fill = BLUE_BG, stroke = BLUE, dash = '', bottom = false } = {}) {
|
||||||
|
return rect(x, y, w, h, { fill, stroke, sw: 2, rx: 10, dash }) + text(x + 14, bottom ? y + h - 18 : y + 20, title, { size: 15, weight: 700, anchor: 'start', fill: stroke === BLUE ? '#05618a' : INK });
|
||||||
|
}
|
||||||
|
function cylinder(x, y, w, h, title, sub = []) {
|
||||||
|
const ry = 12;
|
||||||
|
return `<path d="M${x},${y + ry} v${h - 2 * ry} a${w / 2},${ry} 0 0 0 ${w},0 v${-(h - 2 * ry)}" fill="#fff" stroke="${LINE}" stroke-width="1.5"/>`
|
||||||
|
+ `<ellipse cx="${x + w / 2}" cy="${y + ry}" rx="${w / 2}" ry="${ry}" fill="#fff" stroke="${LINE}" stroke-width="1.5"/>`
|
||||||
|
+ text(x + w / 2, y + h / 2 + 6, [title, ...sub].length > 1 ? [title, ...sub] : title, { size: 13.5 });
|
||||||
|
}
|
||||||
|
const defs = `<defs>
|
||||||
|
<marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="${LINE}"/></marker>
|
||||||
|
<marker id="tri" viewBox="0 0 12 12" refX="11" refY="6" markerWidth="14" markerHeight="14" orient="auto"><path d="M0,0 L12,6 L0,12 z" fill="#fff" stroke="${LINE}" stroke-width="1.2"/></marker>
|
||||||
|
<marker id="open" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M0,0 L10,5 L0,10" fill="none" stroke="${LINE}" stroke-width="1.5"/></marker>
|
||||||
|
</defs>`;
|
||||||
|
/** Straight or elbowed connector with an optional label. `points` is [[x,y], ...]. */
|
||||||
|
function link(points, { label = '', head = 'arr', tail = '', dash = '', lx, ly, size = 12.5 } = {}) {
|
||||||
|
const d = points.map(([x, y], i) => `${i ? 'L' : 'M'}${x},${y}`).join(' ');
|
||||||
|
const path = `<path d="${d}" fill="none" stroke="${LINE}" stroke-width="1.5"${dash ? ` stroke-dasharray="${dash}"` : ''}${head ? ` marker-end="url(#${head})"` : ''}${tail ? ` marker-start="url(#${tail})"` : ''}/>`;
|
||||||
|
if (!label) return path;
|
||||||
|
const mid = Math.floor((points.length - 1) / 2);
|
||||||
|
const x = lx ?? (points[mid][0] + points[mid + 1][0]) / 2;
|
||||||
|
const y = ly ?? (points[mid][1] + points[mid + 1][1]) / 2;
|
||||||
|
const lines = Array.isArray(label) ? label : [label];
|
||||||
|
const width = Math.max(...lines.map((l) => l.length)) * size * 0.56 + 12;
|
||||||
|
const height = lines.length * size * 1.3 + 6;
|
||||||
|
return path + `<rect x="${x - width / 2}" y="${y - height / 2}" width="${width}" height="${height}" fill="#fff" rx="3"/>` + text(x, y, lines, { size, fill: '#333' });
|
||||||
|
}
|
||||||
|
function actor(x, y, label) {
|
||||||
|
return `<g stroke="${INK}" stroke-width="1.8" fill="none"><circle cx="${x}" cy="${y - 34}" r="11"/><path d="M${x},${y - 23} v30 M${x - 20},${y - 12} h40 M${x},${y + 7} l-16,24 M${x},${y + 7} l16,24"/></g>`
|
||||||
|
+ text(x, y + 50, label, { size: 14, weight: 700 });
|
||||||
|
}
|
||||||
|
function usecase(cx, cy, label, w = 230, h = 50) {
|
||||||
|
return `<ellipse cx="${cx}" cy="${cy}" rx="${w / 2}" ry="${h / 2}" fill="#fff" stroke="${BLUE}" stroke-width="1.6"/>` + text(cx, cy, label, { size: 13.5 });
|
||||||
|
}
|
||||||
|
const svg = (w, h, body) => `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">${defs}<rect width="${w}" height="${h}" fill="#fff"/>${body}</svg>`;
|
||||||
|
|
||||||
|
// ─── High-level diagrams ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function architecture() {
|
||||||
|
const b = [];
|
||||||
|
b.push(frame(20, 40, 190, 300, 'ผู้ใช้งาน'));
|
||||||
|
b.push(actor(70, 140, 'Owner'), actor(160, 140, 'Admin'), actor(70, 270, 'Staff'), actor(160, 270, 'Viewer'));
|
||||||
|
b.push(frame(250, 40, 220, 300, 'Client Tier'));
|
||||||
|
b.push(box(270, 80, 180, 110, 'Web Browser', ['Chrome / Edge / Firefox', 'Desktop · Tablet · Mobile', 'หน้าจอ Responsive']));
|
||||||
|
b.push(box(270, 215, 180, 95, 'Socket.IO Client', ['รับการแจ้งเตือน', 'แบบ Real-time']));
|
||||||
|
b.push(frame(510, 20, 420, 420, 'Web Tier — Apache + PHP 8'));
|
||||||
|
b.push(box(530, 55, 380, 58, 'Page Controllers', 'app/<module>/*.php — หน้าจอและฟอร์ม'));
|
||||||
|
b.push(box(530, 125, 380, 58, 'API Engines', 'app/*/api/engine/*.php — รับคำสั่งจากหน้าจอ (JSON)'));
|
||||||
|
b.push(box(530, 195, 380, 70, 'db_auth (ประตูตรวจสอบทุกคำขอ)', ['Session · CSRF · OTP · Role Guard', 'ขอบเขตบริษัท/คลัง (Tenant Scope)'], { fill: '#fff8e5' }));
|
||||||
|
b.push(box(530, 277, 380, 70, 'Manager / Service Classes', ['ตรรกะธุรกิจ: สต๊อก ขาย จัดซื้อ การเงิน บัญชี', 'ทำงานภายใน Database Transaction']));
|
||||||
|
b.push(box(530, 359, 380, 60, 'Report / ETL Managers', 'รายงาน แดชบอร์ด และตารางสรุปยอด'));
|
||||||
|
b.push(frame(970, 40, 210, 300, 'Data Tier — MariaDB'));
|
||||||
|
b.push(cylinder(995, 80, 160, 105, 'wms', ['ผู้ใช้ / บริษัท / สิทธิ์']));
|
||||||
|
b.push(cylinder(995, 205, 160, 115, 'wms2', ['คลัง · ขาย · จัดซื้อ', 'การเงิน · บัญชี']));
|
||||||
|
b.push(frame(510, 480, 420, 190, 'Service Tier — Node.js (pm2)', { bottom: true }));
|
||||||
|
b.push(box(530, 495, 185, 130, 'server.js', ['Socket.IO', 'ส่งการแจ้งเตือน', 'ไปยังผู้ใช้ที่เกี่ยวข้อง']));
|
||||||
|
b.push(box(725, 495, 185, 130, 'scheduler.js', ['สรุปยอดสต๊อก/GL', 'แจ้งเตือนตามเวลา', 'Asia/Bangkok']));
|
||||||
|
b.push(frame(970, 480, 210, 190, 'External', { fill: '#f5f5f5', stroke: GREY }));
|
||||||
|
b.push(box(985, 515, 180, 65, 'Backup', ['Git Backup Remote', 'mysqldump รายวัน']));
|
||||||
|
b.push(box(985, 595, 180, 60, 'SMTP Server', 'อีเมล Onboarding / แจ้งเตือน'));
|
||||||
|
b.push(link([[210, 190], [250, 190]]));
|
||||||
|
b.push(link([[450, 135], [530, 135]], { label: 'HTTPS (TLS)', ly: 120 }));
|
||||||
|
b.push(link([[910, 300], [950, 300], [950, 190], [995, 190]], { label: 'PDO', lx: 950, ly: 245 }));
|
||||||
|
b.push(link([[620, 440], [620, 495]], { label: 'Emit + Secret', lx: 620, ly: 465 }));
|
||||||
|
b.push(link([[820, 495], [820, 440]], { label: 'เรียก app/cron', lx: 820, ly: 465 }));
|
||||||
|
b.push(link([[530, 590], [360, 590], [360, 310]], { label: 'WebSocket', lx: 420, ly: 590 }));
|
||||||
|
b.push(link([[910, 400], [950, 400], [950, 625], [985, 625]], { label: 'SMTP', lx: 950, ly: 470 }));
|
||||||
|
b.push(link([[1075, 320], [1075, 515]], { label: 'สำรองข้อมูล', lx: 1075, ly: 420, dash: '5 4' }));
|
||||||
|
return svg(1200, 690, b.join(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function useCase() {
|
||||||
|
const b = [];
|
||||||
|
b.push(rect(270, 20, 680, 800, { fill: '#fafcfd', stroke: BLUE, sw: 2, rx: 12 }));
|
||||||
|
b.push(text(610, 44, 'ระบบ BRN WMS', { size: 16, weight: 700, fill: '#05618a' }));
|
||||||
|
const A = { Viewer: [100, 150], Staff: [100, 400], Admin: [100, 610], Owner: [100, 765] };
|
||||||
|
// Generalisation: each role inherits the use cases of the role above it.
|
||||||
|
b.push(link([[100, 352], [100, 214]], { head: 'tri' }), link([[100, 562], [100, 464]], { head: 'tri' }), link([[100, 718], [100, 674]], { head: 'tri' }));
|
||||||
|
for (const [name, [x, y]] of Object.entries(A)) b.push(actor(x, y, name));
|
||||||
|
const UC = {
|
||||||
|
login: [460, 95, 'เข้าสู่ระบบ / กู้คืนรหัสผ่าน'],
|
||||||
|
report: [460, 165, 'ดูแดชบอร์ดและรายงาน'],
|
||||||
|
stock: [460, 255, 'รับ / จ่าย / โอนสินค้า'],
|
||||||
|
sales: [460, 320, 'จัดการเอกสารขาย'],
|
||||||
|
purchase: [460, 385, 'จัดการเอกสารจัดซื้อ'],
|
||||||
|
finance: [460, 450, 'รับชำระ / จ่ายชำระ'],
|
||||||
|
gl: [460, 515, 'สมุดรายวันและบัญชีแยกประเภท'],
|
||||||
|
master: [460, 595, 'จัดการข้อมูลหลัก'],
|
||||||
|
users: [460, 660, 'จัดการผู้ใช้และสิทธิ์'],
|
||||||
|
company: [460, 765, 'ตั้งค่าบริษัทและ SMTP'],
|
||||||
|
etl: [790, 205, 'สรุปยอดสต๊อก / GL ตามเวลา'],
|
||||||
|
alert: [790, 280, 'แจ้งเตือนสินค้าใกล้หมด / ค้างชำระ'],
|
||||||
|
notify: [790, 385, 'แจ้งเตือนแบบ Real-time'],
|
||||||
|
email: [790, 660, 'ส่งอีเมล'],
|
||||||
|
};
|
||||||
|
for (const [cx, cy, label] of Object.values(UC)) b.push(usecase(cx, cy, label));
|
||||||
|
const assoc = (actorKey, ucKey) => { const [ax, ay] = A[actorKey]; const [ux, uy] = UC[ucKey]; return link([[ax + 28, ay - 10], [ux - 115, uy]], { head: '' }); };
|
||||||
|
b.push(assoc('Viewer', 'login'), assoc('Viewer', 'report'));
|
||||||
|
for (const k of ['stock', 'sales', 'purchase', 'finance', 'gl']) b.push(assoc('Staff', k));
|
||||||
|
b.push(assoc('Admin', 'master'), assoc('Admin', 'users'), assoc('Owner', 'company'));
|
||||||
|
b.push(actor(1090, 260, 'System Scheduler'), actor(1090, 680, 'SMTP Service'));
|
||||||
|
b.push(link([[1060, 240], [905, 205]], { head: '' }), link([[1060, 250], [905, 280]], { head: '' }), link([[1060, 660], [905, 660]], { head: '' }));
|
||||||
|
b.push(link([[575, 660], [675, 660]], { dash: '6 4', head: 'open', label: '«include»', ly: 645 }));
|
||||||
|
b.push(link([[575, 95], [930, 95], [930, 660], [905, 660]], { dash: '6 4', head: 'open', label: '«include»', lx: 755, ly: 95 }));
|
||||||
|
b.push(link([[575, 320], [700, 370]], { dash: '6 4', head: 'open', label: '«include»', lx: 640, ly: 330 }));
|
||||||
|
return svg(1200, 850, b.join(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function components() {
|
||||||
|
const b = [];
|
||||||
|
const comp = (x, y, w, h, id, name, fill = '#fff') => {
|
||||||
|
const icon = `<g stroke="${LINE}" fill="#fff" stroke-width="1.2"><rect x="${x + w - 30}" y="${y + 10}" width="18" height="22"/><rect x="${x + w - 35}" y="${y + 14}" width="10" height="5"/><rect x="${x + w - 35}" y="${y + 23}" width="10" height="5"/></g>`;
|
||||||
|
return rect(x, y, w, h, { fill, rx: 4 }) + icon + text(x + w / 2 - 8, y + h / 2 - 9, `«component» ${id}`, { size: 12, fill: GREY }) + text(x + w / 2 - 8, y + h / 2 + 12, name, { size: 14, weight: 700 });
|
||||||
|
};
|
||||||
|
b.push(frame(20, 20, 1160, 120, 'ส่วนกลางที่ทุกโมดูลเรียกใช้ (Cross-cutting)'));
|
||||||
|
b.push(comp(45, 55, 350, 70, 'UN01', 'Identity & Access', '#fff'), comp(425, 55, 350, 70, 'UN12', 'Security & Tenant Scope'), comp(805, 55, 350, 70, 'UN02', 'Company Settings'));
|
||||||
|
const W = 255, H = 80, X = [45, 330, 615, 900];
|
||||||
|
b.push(frame(20, 165, 1160, 360, 'โมดูลธุรกิจ (Manager / Service Classes)', { fill: '#fff', stroke: BLUE }));
|
||||||
|
b.push(comp(X[0], 205, W, H, 'UN03', 'Master Data'), comp(X[1], 205, W, H, 'UN04', 'Inventory Control'), comp(X[2], 205, W, H, 'UN05', 'Sales'), comp(X[3], 205, W, H, 'UN06', 'Purchasing'));
|
||||||
|
b.push(comp(X[0], 420, W, H, 'UN10', 'Document Control'), comp(X[1], 420, W, H, 'UN07', 'Finance'), comp(X[2], 420, W, H, 'UN08', 'Accounting / GL'), comp(X[3], 420, W, H, 'UN09', 'Reports & Dashboard'));
|
||||||
|
b.push(frame(20, 550, 1160, 130, 'บริการและโครงสร้างพื้นฐาน', { fill: '#f5f5f5', stroke: GREY }));
|
||||||
|
b.push(comp(45, 585, 350, 75, 'UN11', 'Notification & Scheduler'), comp(425, 585, 350, 75, 'UN13', 'Deployment & Backup'));
|
||||||
|
b.push(cylinder(840, 575, 290, 95, 'MariaDB wms / wms2'));
|
||||||
|
const dep = (pts, label) => link(pts, { dash: '6 4', head: 'open', label, size: 12 });
|
||||||
|
b.push(dep([[X[1], 245], [X[0] + W, 245]], 'ใช้ข้อมูลหลัก'));
|
||||||
|
b.push(dep([[X[2], 230], [X[1] + W, 230]], 'ตัดสต๊อก'));
|
||||||
|
b.push(dep([[X[3] + 60, 205], [X[3] + 60, 180], [X[1] + W - 60, 180], [X[1] + W - 60, 205]], 'รับสินค้าเข้า'));
|
||||||
|
b.push(dep([[X[2] + 128, 285], [X[2] + 128, 420]], 'บันทึก GL'));
|
||||||
|
b.push(dep([[X[3] + 60, 285], [X[3] + 60, 360], [X[2] + 200, 360], [X[2] + 200, 420]], ''));
|
||||||
|
b.push(dep([[X[1] + W, 460], [X[2], 460]], 'บันทึก GL'));
|
||||||
|
b.push(dep([[X[3] + 180, 420], [X[3] + 180, 330], [X[1] + 200, 330], [X[1] + 200, 285]], 'อ่านยอดสต๊อก'));
|
||||||
|
b.push(dep([[X[2] + 60, 285], [X[2] + 60, 390], [X[0] + 200, 390], [X[0] + 200, 420]], 'ออกเลขที่เอกสาร'));
|
||||||
|
b.push(dep([[X[1], 480], [X[0] + W, 480]], ''));
|
||||||
|
b.push(dep([[220, 585], [220, 540], [X[3] + 128, 540], [X[3] + 128, 500]], 'ตารางสรุปยอด'));
|
||||||
|
return svg(1200, 700, b.join(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function deployment() {
|
||||||
|
const b = [];
|
||||||
|
const node = (x, y, w, h, title) => `<path d="M${x},${y + 14} l14,-14 h${w} v${h - 14} l-14,14 h${-w} z M${x},${y + 14} h${w} l14,-14 M${x + w},${y + 14} v${h - 14}" fill="#fff" stroke="${LINE}" stroke-width="1.5"/>` + text(x + 14, y + 36, title, { size: 15, weight: 700, anchor: 'start' });
|
||||||
|
b.push(node(20, 70, 220, 250, '«device» Client'));
|
||||||
|
b.push(box(40, 125, 180, 80, 'Web Browser', ['Desktop / Tablet', 'Mobile']));
|
||||||
|
b.push(box(40, 220, 180, 80, 'เครื่องสแกนบาร์โค้ด', ['(ทำงานเป็นคีย์บอร์ด)']));
|
||||||
|
b.push(node(290, 20, 600, 620, '«execution environment» Production Server — Linux + Docker Compose'));
|
||||||
|
b.push(box(320, 80, 250, 150, '«container» php', ['Apache + PHP 8', 'แอปพลิเคชัน wms-app', 'config.php สร้างจาก .env', 'พอร์ต 80 / 443 (TLS)'], { fill: BLUE_BG, stroke: BLUE }));
|
||||||
|
b.push(box(610, 80, 250, 150, '«container» node', ['pm2', 'server.js (Socket.IO :3000)', 'scheduler.js', '(ETL และแจ้งเตือน)'], { fill: BLUE_BG, stroke: BLUE }));
|
||||||
|
b.push(box(320, 290, 250, 150, '«container» db', ['MariaDB', 'ฐานข้อมูล wms และ wms2', 'Volume ข้อมูลถาวร'], { fill: BLUE_BG, stroke: BLUE }));
|
||||||
|
b.push(box(610, 290, 250, 150, 'ไฟล์ตั้งค่า', ['.env (ไม่อยู่ใน Git)', 'docker/init-env.sh', 'docker-compose.yml'], { fill: '#fff8e5' }));
|
||||||
|
b.push(box(320, 500, 540, 110, 'ทางเลือกติดตั้งแบบ Manual (LAMP)', ['Apache + PHP 8 + MariaDB บนเครื่องเดียว', 'สร้างฐานข้อมูลด้วย setup.php และรัน Node.js ผ่าน pm2'], { dash: '6 4' }));
|
||||||
|
b.push(node(950, 40, 230, 150, '«external» SMTP'));
|
||||||
|
b.push(box(965, 80, 200, 90, ' ', ['ส่งอีเมลของแต่ละบริษัท'], { stroke: 'none', fill: 'none' }));
|
||||||
|
b.push(node(950, 230, 230, 170, '«external» Git Server'));
|
||||||
|
b.push(box(965, 275, 200, 100, ' ', ['Repository หลัก (origin)', 'และ Backup Remote'], { stroke: 'none', fill: 'none' }));
|
||||||
|
b.push(node(950, 440, 230, 170, '«storage» Backup'));
|
||||||
|
b.push(box(965, 485, 200, 100, ' ', ['mysqldump รายวัน 02:00 น.', 'เก็บนอกเครื่องแม่ข่าย'], { stroke: 'none', fill: 'none' }));
|
||||||
|
b.push(link([[220, 165], [320, 165]], { label: 'HTTPS', ly: 150 }));
|
||||||
|
b.push(link([[240, 110], [270, 110], [270, 12], [735, 12], [735, 80]], { label: 'WebSocket', lx: 500, ly: 12 }));
|
||||||
|
b.push(link([[570, 150], [610, 150]], { label: 'Emit', ly: 135, size: 11.5 }));
|
||||||
|
b.push(link([[445, 230], [445, 290]], { label: 'PDO', lx: 445, ly: 260 }));
|
||||||
|
b.push(link([[860, 110], [950, 110]], { label: 'SMTP', ly: 95 }));
|
||||||
|
b.push(link([[860, 330], [950, 330]], { label: 'git push', ly: 315, dash: '5 4' }));
|
||||||
|
b.push(link([[445, 440], [445, 470], [920, 470], [920, 525], [950, 525]], { label: 'สำรองข้อมูล', lx: 700, ly: 470, dash: '5 4' }));
|
||||||
|
return svg(1200, 660, b.join(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Wireframes ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const MENU = ['แดชบอร์ด', 'ข้อมูลหลัก', 'คลังสินค้า', 'ขาย', 'จัดซื้อ', 'การเงิน', 'บัญชี', 'รายงาน', 'ตั้งค่า'];
|
||||||
|
/** Browser window + sidebar + top bar; returns the SVG and the content area. */
|
||||||
|
function shell(title, active, content) {
|
||||||
|
const W = 1200, H = 760;
|
||||||
|
const b = [];
|
||||||
|
b.push(rect(10, 10, W - 20, H - 20, { fill: '#fff', stroke: '#9a9a9a', rx: 10 }));
|
||||||
|
b.push(rect(10, 10, W - 20, 36, { fill: WF_DARK, stroke: '#9a9a9a', rx: 10 }), rect(10, 30, W - 20, 16, { fill: WF_DARK, stroke: 'none', rx: 0 }));
|
||||||
|
b.push(...[30, 50, 70].map((x) => `<circle cx="${x}" cy="28" r="6" fill="#fff" stroke="#9a9a9a"/>`));
|
||||||
|
b.push(rect(100, 18, 520, 20, { fill: '#fff', stroke: '#b5b5b5', rx: 10 }), text(115, 28, 'https://wms.brn.co.th/app/…', { size: 12, anchor: 'start', fill: GREY }));
|
||||||
|
b.push(rect(10, 46, 200, H - 56, { fill: '#e9e9e9', stroke: 'none', rx: 0 }));
|
||||||
|
b.push(rect(28, 62, 120, 30, { fill: '#fff', stroke: '#b5b5b5', rx: 4 }), text(88, 77, 'โลโก้บริษัท', { size: 12, fill: GREY }));
|
||||||
|
MENU.forEach((m, i) => {
|
||||||
|
const y = 116 + i * 40;
|
||||||
|
if (m === active) b.push(rect(18, y - 15, 184, 30, { fill: '#fff', stroke: BLUE, rx: 4 }));
|
||||||
|
b.push(rect(32, y - 7, 14, 14, { fill: m === active ? BLUE : '#bdbdbd', stroke: 'none', rx: 3 }), text(56, y, m, { size: 14, anchor: 'start', weight: m === active ? 700 : 400 }));
|
||||||
|
});
|
||||||
|
b.push(rect(210, 46, W - 220, 50, { fill: '#fff', stroke: WF_DARK, rx: 0 }));
|
||||||
|
b.push(text(232, 71, title, { size: 18, weight: 700, anchor: 'start' }));
|
||||||
|
b.push(rect(W - 330, 58, 150, 26, { fill: WF_FILL, stroke: '#b5b5b5', rx: 4 }), text(W - 255, 71, 'บริษัท ▾', { size: 12.5 }));
|
||||||
|
b.push(`<circle cx="${W - 145}" cy="71" r="13" fill="${WF_DARK}" stroke="#9a9a9a"/>`, text(W - 90, 71, 'ผู้ใช้ ▾', { size: 12.5 }));
|
||||||
|
b.push(content);
|
||||||
|
return svg(W, H, b.join(''));
|
||||||
|
}
|
||||||
|
const btn = (x, y, w, label, primary = false) => rect(x, y, w, 32, { fill: primary ? BLUE : '#fff', stroke: primary ? BLUE : '#9a9a9a', rx: 5 }) + text(x + w / 2, y + 16, label, { size: 13.2, fill: primary ? '#fff' : INK, weight: primary ? 700 : 400 });
|
||||||
|
const field = (x, y, w, label, hint = '') => text(x, y, label, { size: 12.5, anchor: 'start', fill: '#444' }) + rect(x, y + 10, w, 32, { fill: '#fff', stroke: '#b5b5b5', rx: 4 }) + (hint ? text(x + 10, y + 26, hint, { size: 12, anchor: 'start', fill: '#aaa' }) : '');
|
||||||
|
function grid(x, y, cols, rowCount, { rowH = 34, headFill = WF_DARK } = {}) {
|
||||||
|
const w = cols.reduce((a, c) => a + c[1], 0);
|
||||||
|
const b = [rect(x, y, w, rowH, { fill: headFill, stroke: '#b5b5b5', rx: 0 })];
|
||||||
|
let cx = x;
|
||||||
|
for (const [label, cw, align] of cols) { b.push(text(align === 'r' ? cx + cw - 10 : cx + 10, y + rowH / 2, label, { size: 12.5, weight: 700, anchor: align === 'r' ? 'end' : 'start' })); cx += cw; }
|
||||||
|
for (let r = 1; r <= rowCount; r += 1) {
|
||||||
|
b.push(rect(x, y + r * rowH, w, rowH, { fill: r % 2 ? '#fff' : '#fafafa', stroke: '#d0d0d0', rx: 0 }));
|
||||||
|
let px = x;
|
||||||
|
for (const [, cw, align] of cols) { const bw = Math.min(cw - 20, 40 + ((r * 37 + cw) % 60)); b.push(rect(align === 'r' ? px + cw - 10 - bw : px + 10, y + r * rowH + rowH / 2 - 5, bw, 10, { fill: WF_DARK, stroke: 'none', rx: 3 })); px += cw; }
|
||||||
|
}
|
||||||
|
return b.join('');
|
||||||
|
}
|
||||||
|
const tile = (x, y, w, label) => rect(x, y, w, 82, { fill: '#fff', stroke: '#b5b5b5', rx: 6 }) + text(x + 16, y + 24, label, { size: 13.2, anchor: 'start', fill: '#555' }) + rect(x + 16, y + 44, 80, 22, { fill: WF_DARK, stroke: 'none', rx: 4 }) + rect(x + w - 50, y + 22, 32, 32, { fill: WF_FILL, stroke: '#c8c8c8', rx: 6 });
|
||||||
|
|
||||||
|
function wfLogin() {
|
||||||
|
const b = [];
|
||||||
|
b.push(rect(10, 10, 1180, 740, { fill: '#f4f6f8', stroke: '#9a9a9a', rx: 10 }));
|
||||||
|
b.push(rect(400, 110, 400, 520, { fill: '#fff', stroke: '#b5b5b5', rx: 10 }));
|
||||||
|
b.push(rect(530, 140, 140, 50, { fill: WF_FILL, stroke: '#b5b5b5', rx: 6 }), text(600, 165, 'โลโก้', { size: 13.2, fill: GREY }));
|
||||||
|
b.push(text(600, 225, 'เข้าสู่ระบบ BRN WMS', { size: 20, weight: 700 }));
|
||||||
|
b.push(field(440, 265, 320, 'ชื่อผู้ใช้ / อีเมล', 'username'), field(440, 335, 320, 'รหัสผ่าน', '••••••••'));
|
||||||
|
b.push(rect(440, 405, 16, 16, { fill: '#fff', stroke: '#9a9a9a', rx: 3 }), text(465, 413, 'จดจำการเข้าสู่ระบบ', { size: 12.5, anchor: 'start' }));
|
||||||
|
b.push(btn(440, 440, 320, 'เข้าสู่ระบบ', true));
|
||||||
|
b.push(text(600, 500, 'ลืมรหัสผ่าน? · ลงทะเบียนบริษัทใหม่', { size: 13.2, fill: BLUE }));
|
||||||
|
b.push(rect(440, 530, 320, 70, { fill: '#fff8e5', stroke: '#e0c070', rx: 6, dash: '5 4' }), text(600, 565, ['ขั้นตอน OTP (ถ้าเปิดใช้งาน):', 'กรอกรหัส 6 หลักที่ส่งทางอีเมล'], { size: 12.5 }));
|
||||||
|
return svg(1200, 760, b.join(''));
|
||||||
|
}
|
||||||
|
function wfDashboard() {
|
||||||
|
const c = [];
|
||||||
|
['สินค้าทั้งหมด', 'รับเข้า (เดือนนี้)', 'จ่ายออก (เดือนนี้)', 'สินค้าใกล้หมด'].forEach((l, i) => c.push(tile(232 + i * 237, 116, 222, l)));
|
||||||
|
c.push(rect(232, 218, 610, 300, { fill: '#fff', stroke: '#b5b5b5', rx: 6 }), text(250, 240, 'กราฟความเคลื่อนไหวสินค้า 12 เดือน', { size: 13.5, anchor: 'start', weight: 700 }));
|
||||||
|
for (let i = 0; i < 12; i += 1) { const h1 = 40 + ((i * 53) % 150), h2 = 20 + ((i * 31) % 110); c.push(rect(262 + i * 48, 490 - h1, 16, h1, { fill: '#9fd3ea', stroke: 'none', rx: 2 }), rect(280 + i * 48, 490 - h2, 16, h2, { fill: '#c9c9c9', stroke: 'none', rx: 2 })); }
|
||||||
|
c.push(rect(860, 218, 318, 300, { fill: '#fff', stroke: '#b5b5b5', rx: 6 }), text(876, 240, 'กิจกรรมล่าสุด', { size: 13.5, anchor: 'start', weight: 700 }));
|
||||||
|
for (let i = 0; i < 6; i += 1) c.push(`<circle cx="886" cy="${280 + i * 38}" r="10" fill="${WF_DARK}"/>`, rect(906, 272 + i * 38, 170, 10, { fill: WF_DARK, stroke: 'none', rx: 3 }), rect(1110, 272 + i * 38, 50, 10, { fill: '#c9c9c9', stroke: 'none', rx: 3 }));
|
||||||
|
c.push(rect(232, 535, 946, 200, { fill: '#fff', stroke: '#b5b5b5', rx: 6 }), text(250, 557, 'สินค้าใกล้หมด / สินค้าเคลื่อนไหวมากที่สุด', { size: 13.5, anchor: 'start', weight: 700 }));
|
||||||
|
c.push(grid(250, 575, [['สินค้า', 330], ['คลัง', 230], ['คงเหลือ', 170, 'r'], ['จุดสั่งซื้อ', 180, 'r']], 4));
|
||||||
|
return shell('แดชบอร์ด', 'แดชบอร์ด', c.join(''));
|
||||||
|
}
|
||||||
|
function wfList() {
|
||||||
|
const c = [];
|
||||||
|
c.push(btn(1040, 112, 138, '+ สร้างใหม่', true));
|
||||||
|
['ทั้งหมด', 'รอกำหนดคลัง', 'ร่าง', 'ยืนยันแล้ว', 'ยกเลิก'].forEach((l, i) => c.push(tile(232 + i * 190, 158, 176, l)));
|
||||||
|
c.push(rect(232, 258, 946, 56, { fill: WF_FILL, stroke: '#c8c8c8', rx: 6 }));
|
||||||
|
c.push(rect(246, 270, 300, 32, { fill: '#fff', stroke: '#b5b5b5', rx: 4 }), text(260, 286, 'ค้นหาเลขที่เอกสาร / ผู้ติดต่อ', { size: 12, anchor: 'start', fill: '#aaa' }));
|
||||||
|
c.push(rect(560, 270, 170, 32, { fill: '#fff', stroke: '#b5b5b5', rx: 4 }), text(572, 286, 'สถานะ ▾', { size: 12.5, anchor: 'start' }));
|
||||||
|
c.push(rect(744, 270, 250, 32, { fill: '#fff', stroke: '#b5b5b5', rx: 4 }), text(756, 286, 'ช่วงวันที่ DD/MM/YYYY – DD/MM/YYYY', { size: 12, anchor: 'start', fill: '#aaa' }));
|
||||||
|
c.push(btn(1010, 270, 80, 'ค้นหา'), btn(1098, 270, 70, 'ล้าง'));
|
||||||
|
c.push(grid(232, 330, [['เลขที่', 150], ['วันที่', 120], ['ผู้ติดต่อ', 250], ['ยอดรวม', 140, 'r'], ['สถานะ', 140], ['การทำงาน', 146]], 9));
|
||||||
|
c.push(text(232, 695, 'แสดง 1–10 จาก 120 รายการ', { size: 12.5, anchor: 'start', fill: '#555' }));
|
||||||
|
['‹', '1', '2', '3', '›'].forEach((p, i) => c.push(rect(990 + i * 38, 680, 32, 30, { fill: p === '1' ? BLUE : '#fff', stroke: '#b5b5b5', rx: 4 }), text(1006 + i * 38, 695, p, { size: 13.2, fill: p === '1' ? '#fff' : INK })));
|
||||||
|
return shell('ใบสั่งขาย (หน้ารายการ)', 'ขาย', c.join(''));
|
||||||
|
}
|
||||||
|
function wfDocument() {
|
||||||
|
const c = [];
|
||||||
|
c.push(rect(232, 112, 946, 150, { fill: '#fff', stroke: '#b5b5b5', rx: 6 }), text(250, 132, 'ข้อมูลเอกสาร', { size: 13.5, anchor: 'start', weight: 700 }));
|
||||||
|
c.push(field(250, 152, 280, 'ผู้ติดต่อ *', 'ค้นหาผู้ติดต่อ…'), field(550, 152, 180, 'วันที่เอกสาร *', 'DD/MM/YYYY'), field(750, 152, 200, 'แผนก *', 'เลือกแผนก ▾'), field(970, 152, 190, 'สถานะ', 'ร่าง'));
|
||||||
|
c.push(field(250, 208, 910, 'หมายเหตุ', ''));
|
||||||
|
c.push(rect(232, 278, 946, 300, { fill: '#fff', stroke: '#b5b5b5', rx: 6 }), text(250, 298, 'รายการสินค้า', { size: 13.5, anchor: 'start', weight: 700 }), btn(1060, 284, 104, '+ เพิ่มรายการ'));
|
||||||
|
c.push(grid(250, 322, [['SKU', 110], ['สินค้า', 240], ['คลัง', 130], ['จำนวน', 90, 'r'], ['ราคา/หน่วย', 110, 'r'], ['ภาษี %', 70, 'r'], ['รวม', 110, 'r'], ['', 40]], 5));
|
||||||
|
c.push(rect(232, 594, 946, 150, { fill: '#fff', stroke: '#b5b5b5', rx: 6 }));
|
||||||
|
['ยอดรวมก่อนภาษี', 'ส่วนลด', 'ภาษีมูลค่าเพิ่ม', 'ค่าขนส่ง'].forEach((l, i) => c.push(text(900, 612 + i * 26, l, { size: 12.5, anchor: 'end', fill: '#555' }), rect(920, 603 + i * 26, 240, 18, { fill: WF_FILL, stroke: '#c8c8c8', rx: 3 })));
|
||||||
|
c.push(text(250, 616, 'ยอดสุทธิ', { size: 14, anchor: 'start', weight: 700 }), rect(250, 632, 200, 34, { fill: WF_DARK, stroke: 'none', rx: 4 }));
|
||||||
|
c.push(btn(250, 690, 110, 'บันทึกร่าง', true), btn(372, 690, 110, 'ยืนยัน'), btn(494, 690, 90, 'พิมพ์'), btn(596, 690, 90, 'ยกเลิก'));
|
||||||
|
return shell('ใบสั่งขาย (หน้าบันทึก/แก้ไข)', 'ขาย', c.join(''));
|
||||||
|
}
|
||||||
|
function wfStockIn() {
|
||||||
|
const c = [];
|
||||||
|
c.push(rect(232, 112, 600, 620, { fill: '#fff', stroke: '#b5b5b5', rx: 6 }), text(250, 134, 'บันทึกรับสินค้าเข้า', { size: 13.5, anchor: 'start', weight: 700 }));
|
||||||
|
c.push(field(250, 156, 270, 'คลังสินค้า *', 'เลือกคลัง ▾'), field(540, 156, 270, 'วันที่รับ *', 'DD/MM/YYYY'));
|
||||||
|
c.push(field(250, 216, 460, 'สินค้า (พิมพ์หรือสแกนบาร์โค้ด) *', 'SKU / ชื่อสินค้า'), btn(720, 226, 90, 'สแกน'));
|
||||||
|
c.push(field(250, 276, 170, 'จำนวน *', '0.0000'), field(440, 276, 170, 'Lot Number', ''), field(630, 276, 180, 'วันหมดอายุ', 'DD/MM/YYYY'));
|
||||||
|
c.push(field(250, 336, 560, 'Serial Number', 'คั่นด้วยเครื่องหมายจุลภาค'));
|
||||||
|
c.push(text(250, 400, 'ตำแหน่งจัดเก็บ *', { size: 12.5, anchor: 'start', fill: '#444' }));
|
||||||
|
c.push(rect(250, 410, 180, 32, { fill: '#fff', stroke: '#b5b5b5', rx: 4 }), text(262, 426, 'Zone ▾', { size: 12.5, anchor: 'start' }), rect(440, 410, 180, 32, { fill: '#fff', stroke: '#b5b5b5', rx: 4 }), text(452, 426, 'Aisle ▾', { size: 12.5, anchor: 'start' }), rect(630, 410, 180, 32, { fill: '#fff', stroke: '#b5b5b5', rx: 4 }), text(642, 426, 'Bin ▾', { size: 12.5, anchor: 'start' }));
|
||||||
|
c.push(field(250, 462, 560, 'เอกสารอ้างอิง', 'เลขที่ PO / ใบส่งของ'));
|
||||||
|
c.push(rect(250, 528, 560, 80, { fill: WF_FILL, stroke: '#b5b5b5', rx: 6, dash: '6 4' }), text(530, 568, ['แนบไฟล์ (ลากวางหรือคลิกเพื่อเลือก)', 'ไฟล์ที่อนุญาต: PDF, JPG, PNG'], { size: 12.5, fill: '#777' }));
|
||||||
|
c.push(btn(250, 640, 120, 'บันทึกร่าง', true), btn(382, 640, 120, 'อนุมัติรับเข้า'), btn(514, 640, 90, 'ยกเลิก'));
|
||||||
|
c.push(rect(850, 112, 328, 620, { fill: '#fff', stroke: '#b5b5b5', rx: 6 }), text(866, 134, 'ข้อมูลช่วยตัดสินใจ', { size: 13.5, anchor: 'start', weight: 700 }));
|
||||||
|
c.push(tile(866, 156, 296, 'ยอดคงเหลือในคลังนี้'), tile(866, 252, 296, 'ช่องจัดเก็บที่ว่าง'));
|
||||||
|
c.push(text(866, 370, 'ประวัติความเคลื่อนไหวล่าสุด', { size: 13.2, anchor: 'start', weight: 700 }), grid(866, 386, [['วันที่', 110], ['ประเภท', 90], ['จำนวน', 96, 'r']], 6));
|
||||||
|
return shell('รับสินค้าเข้า (Stock-in)', 'คลังสินค้า', c.join(''));
|
||||||
|
}
|
||||||
|
function wfReport() {
|
||||||
|
const c = [];
|
||||||
|
c.push(rect(232, 112, 946, 96, { fill: WF_FILL, stroke: '#c8c8c8', rx: 6 }), text(250, 132, 'ตัวกรองรายงาน', { size: 13.5, anchor: 'start', weight: 700 }));
|
||||||
|
c.push(rect(250, 148, 200, 32, { fill: '#fff', stroke: '#b5b5b5', rx: 4 }), text(262, 164, 'คลังสินค้า ▾', { size: 12.5, anchor: 'start' }));
|
||||||
|
c.push(rect(462, 148, 200, 32, { fill: '#fff', stroke: '#b5b5b5', rx: 4 }), text(474, 164, 'หมวดสินค้า ▾', { size: 12.5, anchor: 'start' }));
|
||||||
|
c.push(rect(674, 148, 250, 32, { fill: '#fff', stroke: '#b5b5b5', rx: 4 }), text(686, 164, 'ช่วงวันที่', { size: 12.5, anchor: 'start', fill: '#aaa' }));
|
||||||
|
c.push(btn(936, 148, 70, 'แสดง', true), btn(1014, 148, 70, 'พิมพ์'), btn(1092, 148, 76, 'ส่งออก'));
|
||||||
|
['ยอดยกมา', 'รับเข้า', 'จ่ายออก', 'คงเหลือ'].forEach((l, i) => c.push(tile(232 + i * 237, 224, 222, l)));
|
||||||
|
c.push(grid(232, 324, [['SKU', 130], ['สินค้า', 260], ['คลัง', 160], ['ยกมา', 100, 'r'], ['รับเข้า', 100, 'r'], ['จ่ายออก', 100, 'r'], ['คงเหลือ', 96, 'r']], 10));
|
||||||
|
c.push(text(232, 715, 'รายงานแสดงเฉพาะบริษัทและคลังที่ผู้ใช้ได้รับสิทธิ์', { size: 12.5, anchor: 'start', fill: '#555' }));
|
||||||
|
return shell('รายงานความเคลื่อนไหวสินค้า', 'รายงาน', c.join(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** [file, caption, svg] — in the order they appear in the Software Design document. */
|
||||||
|
export const FIGURES = {
|
||||||
|
architecture: ['hld-architecture', 'รูปที่ 1 High Level Architecture Diagram — โครงสร้างระบบแบ่งตามชั้นการทำงาน', architecture],
|
||||||
|
useCase: ['hld-use-case', 'รูปที่ 2 Use Case Diagram — ผู้ใช้งานและกลุ่มการใช้งานหลัก', useCase],
|
||||||
|
component: ['hld-component', 'รูปที่ 3 Component Diagram — โมดูลของระบบและความสัมพันธ์', components],
|
||||||
|
deployment: ['hld-deployment', 'รูปที่ 4 Deployment Diagram — การติดตั้งบนสภาพแวดล้อมใช้งานจริง', deployment],
|
||||||
|
wfLogin: ['wf-login', 'รูปที่ 5 Wireframe หน้าจอเข้าสู่ระบบ', wfLogin],
|
||||||
|
wfDashboard: ['wf-dashboard', 'รูปที่ 6 Wireframe หน้าจอแดชบอร์ด', wfDashboard],
|
||||||
|
wfList: ['wf-list', 'รูปที่ 7 Wireframe หน้ารายการเอกสาร (ตัวอย่างใบสั่งขาย) — ใช้รูปแบบเดียวกันทุกเอกสาร', wfList],
|
||||||
|
wfDocument: ['wf-document', 'รูปที่ 8 Wireframe หน้าบันทึกเอกสาร (ตัวอย่างใบสั่งขาย) — ใช้กับเอกสารขาย จัดซื้อ และการเงิน', wfDocument],
|
||||||
|
wfStockIn: ['wf-stock-in', 'รูปที่ 9 Wireframe หน้าจอรับสินค้าเข้า', wfStockIn],
|
||||||
|
wfReport: ['wf-report', 'รูปที่ 10 Wireframe หน้าจอรายงาน', wfReport],
|
||||||
|
};
|
||||||
|
/** Markdown image line for a figure; the path is relative to the document's folder. */
|
||||||
|
export const figure = (key) => `![${FIGURES[key][1]}](figures/${FIGURES[key][0]}.png)`;
|
||||||
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 270 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 150 KiB |
@@ -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.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.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'],
|
['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.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'],
|
['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.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.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'],
|
['3.4', 'Project Execution', 'Authentication & Onboarding', 'พัฒนาเข้าสู่ระบบ ลงทะเบียน Onboarding และสิทธิ์ตามบทบาท', ['ThS'], '2026-04-28', '2026-05-12', 'Authentication modules', 'Completed', 'Git d8fd30c–79e66bd'],
|
||||||
@@ -24,7 +24,7 @@ export const TASKS = [
|
|||||||
['4.1', 'Project Control', 'Progress & Meeting Records', 'ติดตามความก้าวหน้า ประเด็นปัญหา มติ และการแก้ไขตลอดโครงการ', ['ApS', 'YaB'], '2026-01-05', '2026-08-24', 'Progress Status, Meeting Record', 'Completed', 'บันทึกรายงวดตลอดโครงการ'],
|
['4.1', 'Project Control', 'Progress & Meeting Records', 'ติดตามความก้าวหน้า ประเด็นปัญหา มติ และการแก้ไขตลอดโครงการ', ['ApS', 'YaB'], '2026-01-05', '2026-08-24', 'Progress Status, Meeting Record', 'Completed', 'บันทึกรายงวดตลอดโครงการ'],
|
||||||
['4.2', 'Project Control', 'Configuration & Repository Control', 'ควบคุม Source Code, Baseline, เวอร์ชันเอกสาร และการสำรองข้อมูล', ['ThS', 'YaB'], '2026-02-19', '2026-08-24', 'Software Configuration', 'Completed', 'Git origin + backup remote'],
|
['4.2', 'Project Control', 'Configuration & Repository Control', 'ควบคุม Source Code, Baseline, เวอร์ชันเอกสาร และการสำรองข้อมูล', ['ThS', 'YaB'], '2026-02-19', '2026-08-24', 'Software Configuration', 'Completed', 'Git origin + backup remote'],
|
||||||
['4.3', 'Verification', 'Verify Work Products', 'ตรวจสอบความต้องการ ออกแบบ ส่วนประกอบ และการสอบกลับ', ['PaNg', 'NoC'], '2026-05-30', '2026-08-17', 'Verification Results', 'Completed', 'ตรวจสอบ 4 รอบ รอบสุดท้าย 17 ส.ค. 69'],
|
['4.3', 'Verification', 'Verify Work Products', 'ตรวจสอบความต้องการ ออกแบบ ส่วนประกอบ และการสอบกลับ', ['PaNg', 'NoC'], '2026-05-30', '2026-08-17', 'Verification Results', 'Completed', 'ตรวจสอบ 4 รอบ รอบสุดท้าย 17 ส.ค. 69'],
|
||||||
['4.4', 'Validation', 'System Test & UAT', 'ทดสอบระบบตาม Test Case และทดสอบการยอมรับโดยผู้ใช้', ['PaNg', 'SeV'], '2026-08-10', '2026-08-14', 'Test Report, Validation Results', 'Completed', 'ทดสอบ 45 Test Case และ 12 สถานการณ์ UAT'],
|
['4.4', 'Validation', 'System Test & UAT', 'ทดสอบระบบตาม Test Case และทดสอบการยอมรับโดยผู้ใช้', ['PaNg', 'SeV'], '2026-08-10', '2026-08-14', 'Test Report, Validation Results', 'Completed', `ทดสอบ 45 Test Case และ UAT ครบ 14 หมวดความต้องการ (${CRS.length} รายการ)`],
|
||||||
['4.5', 'Documentation', 'Operational Documentation', 'จัดทำคู่มือผู้ใช้ คู่มือผู้ดูแลระบบ และคู่มือบำรุงรักษา', ['ThS', 'YaB'], '2026-06-01', '2026-08-17', 'User / Operation / Maintenance docs', 'Completed', 'จัดทำครบ 3 ฉบับ'],
|
['4.5', 'Documentation', 'Operational Documentation', 'จัดทำคู่มือผู้ใช้ คู่มือผู้ดูแลระบบ และคู่มือบำรุงรักษา', ['ThS', 'YaB'], '2026-06-01', '2026-08-17', 'User / Operation / Maintenance docs', 'Completed', 'จัดทำครบ 3 ฉบับ'],
|
||||||
['4.6', 'Stabilization', 'Post-baseline Corrections', 'แก้ไขปัญหาการเข้าสู่ระบบและค่าตั้งค่าหลัง Baseline', ['ThS'], '2026-08-03', '2026-08-03', 'Correction Register', 'Completed', 'Git b2c4374'],
|
['4.6', 'Stabilization', 'Post-baseline Corrections', 'แก้ไขปัญหาการเข้าสู่ระบบและค่าตั้งค่าหลัง Baseline', ['ThS'], '2026-08-03', '2026-08-03', 'Correction Register', 'Completed', 'Git b2c4374'],
|
||||||
['4.7', 'Stabilization', 'Demonstration Data & Delivery Package', 'เตรียมข้อมูลสาธิต ปรับแบรนด์ และชุดติดตั้ง Docker Compose', ['ThS'], '2026-08-14', '2026-08-17', 'Demo data, Docker stack', 'Completed', 'Git dd48a8b–6c39700'],
|
['4.7', 'Stabilization', 'Demonstration Data & Delivery Package', 'เตรียมข้อมูลสาธิต ปรับแบรนด์ และชุดติดตั้ง Docker Compose', ['ThS'], '2026-08-14', '2026-08-17', 'Demo data, Docker stack', 'Completed', 'Git dd48a8b–6c39700'],
|
||||||
@@ -66,56 +66,67 @@ export const CORRECTIONS = [
|
|||||||
['ISS-028', '2026-07-28', '2026-08-03', 'สูง', 'การเข้าสู่ระบบและค่าตั้งค่าสภาพแวดล้อมผิดพลาดหลัง Baseline', 'ค่าตั้งค่าของสภาพแวดล้อมใหม่ไม่ตรงกับที่ระบบคาดหวัง', 'แก้ไขการเข้าสู่ระบบและค่าตั้งค่าสภาพแวดล้อม', 'ThS', 'b2c4374', 'TC-UN12.004'],
|
['ISS-028', '2026-07-28', '2026-08-03', 'สูง', 'การเข้าสู่ระบบและค่าตั้งค่าสภาพแวดล้อมผิดพลาดหลัง Baseline', 'ค่าตั้งค่าของสภาพแวดล้อมใหม่ไม่ตรงกับที่ระบบคาดหวัง', 'แก้ไขการเข้าสู่ระบบและค่าตั้งค่าสภาพแวดล้อม', 'ThS', 'b2c4374', 'TC-UN12.004'],
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Change Reports: one document each. */
|
/**
|
||||||
export const CHANGES = [
|
* Change control. A change request is raised only for something the delivery cannot do without:
|
||||||
{
|
* it adds, removes or alters a deliverable, and so moves Man-day, schedule or cost.
|
||||||
id: 'CH-001', date: '2026-05-21', file: 'Rack to Bin Rename',
|
* Adjustments that fail that test are handled as decisions or tasks and listed here as reviewed.
|
||||||
titleTh: 'เปลี่ยนคำเรียกตำแหน่งจัดเก็บจาก Rack เป็น Bin',
|
*/
|
||||||
requester: P.NoC, unit: 'ฝ่ายวิเคราะห์ระบบ / ตัวแทนผู้ใช้งานคลังสินค้า',
|
export const CHANGE_CRITERIA = [
|
||||||
type: 'Scope', typeChecked: 'Quality', priority: 'Medium',
|
['เป็นสิ่งจำเป็นต่อการส่งมอบ', 'หากไม่ดำเนินการ จะส่งมอบสิ่งส่งมอบ (Work Product) หรือความต้องการที่ตกลงไว้ไม่ได้'],
|
||||||
description: 'เปลี่ยนคำเรียกตำแหน่งจัดเก็บสินค้าจาก "Rack" เป็น "Bin" ทั้งระบบ ครอบคลุมป้ายกำกับหน้าจอ, custom.js, เครื่องมือบาร์โค้ด (BarcodeManager.php, location_barcode_label.php, retrieve_rack.php) และคลาสจัดการคลังสินค้า สต๊อก ใบสั่งขาย รายงาน ใบรับคืน ใบคืนผู้ขาย สินค้า และการตั้งค่าบริษัท',
|
['เปลี่ยนแปลงขอบเขตหรือสิ่งส่งมอบ', 'เพิ่ม ลด หรือแก้ไขความต้องการ (CR) หรือสิ่งส่งมอบที่ตั้ง Baseline แล้ว เช่น เพิ่มโมดูล หรือรองรับ 2 ภาษา'],
|
||||||
justification: 'ให้คำที่ใช้ในระบบตรงกับคำที่พนักงานคลังสินค้าใช้จริงในการปฏิบัติงาน ลดความสับสนระหว่างการใช้งานและการอบรม',
|
['กระทบทรัพยากรของโครงการ', 'กระทบ Man-day กำหนดส่งมอบ หรือค่าใช้จ่าย จึงต้องประเมินผลกระทบและขออนุมัติก่อนดำเนินการ'],
|
||||||
impact: [
|
];
|
||||||
['ผลกระทบต่อ Scope', 'แก้ไข 14 ไฟล์ ประมาณ 1,000 บรรทัด ครอบคลุมคลาสจัดการ 8 คลาสและเครื่องมือบาร์โค้ด ไม่มีการเพิ่มฟังก์ชันใหม่'],
|
/** Adjustments reviewed against CHANGE_CRITERIA: [date, topic, requesterId, assessment, handledAs, reference]. */
|
||||||
['ผลกระทบต่อ Schedule', 'ดำเนินการภายในช่วงพัฒนาปัจจุบัน ไม่กระทบกำหนดส่งมอบ'],
|
export const CHANGE_REVIEW = [
|
||||||
['ผลกระทบต่อ Budget/Cost', 'ไม่มีค่าใช้จ่ายเพิ่ม ใช้ทรัพยากรทีมพัฒนาเดิม'],
|
['2026-05-23', 'ปรับคำเรียกตำแหน่งจัดเก็บจาก Rack เป็น Bin ทั้งระบบ', 'NoC',
|
||||||
['ผลกระทบต่อ Resource', 'ผู้พัฒนา 1 คน'],
|
'เป็นการปรับคำเรียกบนหน้าจอให้ตรงกับการปฏิบัติงานจริง ไม่เพิ่มหรือลดฟังก์ชัน ไม่กระทบ Man-day และส่งมอบได้แม้ไม่ปรับ',
|
||||||
['ผลกระทบต่อ Quality/Deliverables', 'เป็นการเปลี่ยนคำเรียกเท่านั้น ไม่เปลี่ยนพฤติกรรมของระบบ ข้อบกพร่องที่เกิดจากการเปลี่ยนไม่ครบให้บันทึกใน Correction Register (ISS-023)'],
|
'มติที่ประชุม ดำเนินการภายใน Task 3.9', 'Meeting Record 23 พ.ค. 69, Correction Register ISS-023'],
|
||||||
],
|
['2026-08-03', 'เตรียมข้อมูลสาธิตสำหรับการตรวจรับและการสาธิตระบบ', 'ApS',
|
||||||
commit: '8f57ab5', result: 'ดำเนินการแล้วเสร็จ และตรวจสอบผลด้วย TC-UN03.001 ผ่านเมื่อ 10–14 ส.ค. 69',
|
'เป็นข้อมูลตั้งต้นสำหรับตรวจรับ ไม่ใช่สิ่งส่งมอบ และต้องแยกจากข้อมูลใช้งานจริงตาม CR07:004',
|
||||||
},
|
'งานเตรียมการส่งมอบ Task 4.7', 'Meeting Record 3 ส.ค. 69, 14 ส.ค. 69'],
|
||||||
{
|
['2026-08-14', 'ปรับอัตลักษณ์องค์กรและจัดทำชุดติดตั้ง Docker Compose', 'SeV',
|
||||||
id: 'CH-002', date: '2026-08-08', file: 'Demo Data Population',
|
'ชุดติดตั้ง Docker Compose อยู่ในความต้องการ CR10:002 ตั้งแต่ Baseline และการปรับอัตลักษณ์องค์กรไม่เปลี่ยนแปลงฟังก์ชันหรือสิ่งส่งมอบ',
|
||||||
titleTh: 'เพิ่มชุดข้อมูลสาธิตสำหรับการตรวจรับและสาธิตระบบ',
|
'งานเตรียมการส่งมอบ Task 4.7', 'Meeting Record 14 ส.ค. 69, 17 ส.ค. 69'],
|
||||||
requester: P.ApS, unit: 'ฝ่ายบริหารโครงการ / เตรียมการส่งมอบ',
|
];
|
||||||
type: 'Scope', typeChecked: 'Quality', priority: 'Medium',
|
|
||||||
description: 'เพิ่มสคริปต์สร้างข้อมูลสาธิต 3 ไฟล์ (demo_seed.php, demo_seed_more_orders.php, demo_seed_transactions.php) สำหรับใบสั่งขาย รูปแบบคำสั่งซื้อเพิ่มเติม และประวัติรายการเคลื่อนไหว พร้อมตั้งค่าไดเรกทอรี Log ของบริการ Node.js ที่ใช้ระหว่างสร้างข้อมูล',
|
/**
|
||||||
justification: 'ระบบที่ติดตั้งใหม่ไม่มีประวัติรายการ ทำให้ตรวจรับรายงานและขั้นตอนการทำงานไม่ได้ จึงต้องมีข้อมูลตัวอย่างที่ใกล้เคียงการใช้งานจริงสำหรับการตรวจรับและการสาธิต',
|
* Verification rounds, planned in the Software Project Plan and recorded in Verification Results.
|
||||||
impact: [
|
* The 232-day project is reviewed about every two months, with a final round before delivery.
|
||||||
['ผลกระทบต่อ Scope', 'แก้ไข 9 ไฟล์ เพิ่มประมาณ 2,900 บรรทัด เป็นสคริปต์สร้างข้อมูลใหม่ ไม่แก้ตรรกะหลักของระบบ'],
|
*/
|
||||||
['ผลกระทบต่อ Schedule', 'ดำเนินการภายในช่วงเตรียมส่งมอบตามแผน'],
|
export const VERIFICATION_ROUNDS = [
|
||||||
['ผลกระทบต่อ Budget/Cost', 'ไม่มีค่าใช้จ่ายเพิ่ม'],
|
{ v: 'V0.1', date: '2026-03-17', no: 1, time: '09:00 – 12:00 น.', hours: 3, topic: 'ตรวจสอบเอกสารวางแผนโครงการและความต้องการ', wps: ['WP 1.0', 'WP 2.0'] },
|
||||||
['ผลกระทบต่อ Resource', 'ผู้พัฒนา 1 คน'],
|
{ v: 'V0.2', date: '2026-05-29', no: 2, time: '09:00 – 12:00 น.', hours: 3, topic: 'ตรวจสอบเอกสารความต้องการซอฟต์แวร์และการออกแบบ', wps: ['WP 3.0', 'WP 4.0', 'WP 5.0'] },
|
||||||
['ผลกระทบต่อ Quality/Deliverables', 'เป็นการเพิ่มข้อมูลสาธิตเท่านั้น ข้อมูลสาธิตต้องแยกจากข้อมูลใช้งานจริงตาม CR07:004'],
|
{ v: 'V0.3', date: '2026-07-31', no: 3, time: '09:00 – 12:00 น.', hours: 3, topic: 'ตรวจสอบชุดทดสอบและการสอบกลับ', wps: ['WP 6.0'] },
|
||||||
],
|
{ v: 'V1.0', date: '2026-08-17', no: 4, time: '09:00 – 16:00 น.', hours: 6, topic: 'User Acceptance Test (UAT) และตรวจสอบเอกสารส่งมอบทั้งหมด', wps: ['WP 1.0', 'WP 2.0', 'WP 3.0', 'WP 4.0', 'WP 5.0', 'WP 6.0', 'WP 7.0', 'WP 8.0', 'WP 9.0', 'WP 10.0', 'WP 11.0'] },
|
||||||
commit: 'dd48a8b', result: 'ดำเนินการแล้วเสร็จเมื่อ 14 ส.ค. 69 และใช้เป็นข้อมูลตั้งต้นของการทดสอบและ UAT',
|
];
|
||||||
},
|
/** Rules every verification round follows (plan section and each Verification Results record). */
|
||||||
{
|
export const VERIFICATION_RULES = [
|
||||||
id: 'CH-003', date: '2026-08-10', file: 'Delivery Preparation Bundle',
|
'เจ้าหน้าที่ควบคุมเอกสารต้องตรวจสอบแต่ละรายการอย่างละเอียด และต้องมีหลักฐานเพียงพอที่จะสรุปผลว่าผ่าน',
|
||||||
titleTh: 'ปรับอัตลักษณ์องค์กรและเพิ่มชุดติดตั้ง Docker Compose',
|
'ผู้ปฏิบัติงานที่เกี่ยวข้องควรร่วมรับฟังผลการตรวจสอบ เพื่อให้แก้ไขได้ทันทีเมื่อพบประเด็น',
|
||||||
requester: P.SeV, unit: 'ผู้บริหาร / ฝ่ายพัฒนา',
|
'การตรวจสอบแต่ละครั้งไม่ควรถูกขัดจังหวะ ซึ่งจะทำให้การตรวจสอบเลื่อนออกไป',
|
||||||
type: 'Scope', typeChecked: 'Quality', priority: 'Medium',
|
'ห้ามให้ผู้อื่นตรวจสอบแทนผู้ที่ได้รับมอบหมาย',
|
||||||
description: 'รวมการเปลี่ยนแปลงเพื่อเตรียมส่งมอบ 2 รายการ: (1) เปลี่ยนชุดอัตลักษณ์องค์กร ได้แก่ โลโก้ (logo.png, logo.svg), Favicon และภาพหัวเอกสาร พร้อมปรับไฟล์ UI ที่อ้างอิง และ (2) เพิ่มชุดติดตั้ง Docker Compose สำหรับใช้งานจริง ประกอบด้วย Compose file, Dockerfile, แม่แบบค่าตั้งค่า, Entrypoint, SQL เริ่มต้น, .env.example และสคริปต์สร้าง .env แบบโต้ตอบ',
|
];
|
||||||
justification: 'ให้ชุดส่งมอบใช้อัตลักษณ์องค์กรของลูกค้าอย่างถูกต้อง และมีเส้นทางการติดตั้งที่ทำซ้ำได้ โดยไม่นำค่าความลับเข้า Source Control หรือฝังไว้ใน Image',
|
|
||||||
impact: [
|
/**
|
||||||
['ผลกระทบต่อ Scope', 'ปรับแบรนด์ 23 ไฟล์ (ภาพและไฟล์ PHP/CSS 11 ไฟล์) และเพิ่มไฟล์ติดตั้ง 10 ไฟล์'],
|
* Lessons drawn from the Correction Register at project close: what the team changes next time.
|
||||||
['ผลกระทบต่อ Schedule', 'ดำเนินการภายในช่วงเตรียมส่งมอบ 15–17 ส.ค. 69'],
|
* [theme, issue ids, what happened, preventive action for the next project]
|
||||||
['ผลกระทบต่อ Budget/Cost', 'ไม่มีค่าใช้จ่ายเพิ่ม'],
|
*/
|
||||||
['ผลกระทบต่อ Resource', 'ผู้พัฒนา 1 คน'],
|
export const LESSONS = [
|
||||||
['ผลกระทบต่อ Quality/Deliverables', 'ภาพหัวเอกสารถูกใช้ในเอกสารส่งมอบ ชุด Docker Compose สร้างค่าความลับจาก .env ขณะเริ่ม Container ตาม CR10:003'],
|
['ความปลอดภัยและการควบคุมสิทธิ์', ['ISS-001', 'ISS-003', 'ISS-004', 'ISS-006', 'ISS-009', 'ISS-011', 'ISS-015', 'ISS-016', 'ISS-017', 'ISS-018', 'ISS-024', 'ISS-026'],
|
||||||
],
|
'ข้อบกพร่องกลุ่มใหญ่ที่สุดเกิดจากการเพิ่ม Role Guard การตรวจสิทธิ์ และการจำกัด Session ทีละส่วนระหว่างพัฒนา',
|
||||||
commit: '63cea23, 136084f, 6c39700', result: 'ดำเนินการแล้วเสร็จเมื่อ 17 ส.ค. 69 และตรวจสอบผลด้วย TC-UN13.002 และ TC-UN13.003',
|
'กำหนด Secure Coding Checklist และรูปแบบการตรวจสิทธิ์กลางตั้งแต่ช่วงออกแบบ และทบทวนโค้ดด้านความปลอดภัยทุกโมดูลก่อนรวมเข้า Baseline'],
|
||||||
},
|
['ขั้นตอน Onboarding และการลงทะเบียน', ['ISS-007', 'ISS-008', 'ISS-013'],
|
||||||
|
'เส้นทางการรับคำเชิญมีหลายเงื่อนไขที่ไม่ได้ระบุครบในการออกแบบ',
|
||||||
|
'เขียน Test Case ครบทุกเส้นทางของขั้นตอนหลายสถานะก่อนพัฒนา และทดสอบซ้ำทุกครั้งที่แก้ไข'],
|
||||||
|
['ความสอดคล้องระหว่างข้อกำหนดกับการพัฒนา', ['ISS-019', 'ISS-020', 'ISS-022'],
|
||||||
|
'ข้อกำหนดและเอกสารถูกปรับระหว่างพัฒนา ทำให้เกิดช่องว่างกับโค้ด',
|
||||||
|
'ปรับข้อกำหนดและ Traceability Record ในรอบเดียวกับการแก้โค้ด และตรวจความสอดคล้องในทุกรอบ Verification'],
|
||||||
|
['ความถูกต้องของข้อมูลสต๊อกและบัญชี', ['ISS-002', 'ISS-012'],
|
||||||
|
'การแปลงชนิดข้อมูลและการตรวจสอบความสัมพันธ์ของข้อมูลไม่ครอบคลุม',
|
||||||
|
'ตรวจสอบข้อมูลนำเข้าและความสัมพันธ์ฝั่งเซิร์ฟเวอร์ทุกคำสั่งที่เปลี่ยนสต๊อกหรือ GL และเพิ่มชุดทดสอบวงจรเอกสาร'],
|
||||||
|
['มาตรฐานโค้ดและการตั้งค่าสภาพแวดล้อม', ['ISS-005', 'ISS-010', 'ISS-014', 'ISS-021', 'ISS-023', 'ISS-025', 'ISS-027', 'ISS-028'],
|
||||||
|
'พัฒนาหลายโมดูลคู่ขนานโดยไม่มีมาตรฐานการตั้งชื่อ เส้นทางไฟล์ และค่าตั้งค่ากลาง',
|
||||||
|
'กำหนด Coding Standard และแม่แบบค่าตั้งค่าตั้งแต่ต้นโครงการ และทดสอบการติดตั้งบนสภาพแวดล้อมใหม่ทุกครั้งก่อนตั้ง Baseline'],
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,7 +135,7 @@ export const CHANGES = [
|
|||||||
*/
|
*/
|
||||||
export const PERIODS = [
|
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-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-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-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: 'ติดตามผลการแก้ไขด้านความปลอดภัยและการออกแบบฐานข้อมูลสต๊อก' }],
|
['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: 'ติดตามผลการแก้ไขด้านความปลอดภัยและการออกแบบฐานข้อมูลสต๊อก' }],
|
||||||
@@ -133,11 +144,11 @@ export const PERIODS = [
|
|||||||
['2026-05-08', '2026-04-30', '2026-05-08', 8, [['3.4', 'In Progress', 80, 'พัฒนาการเข้าสู่ระบบและสิทธิ์ตามบทบาทเกือบครบ เหลือขั้นตอนกู้คืนรหัสผ่าน'], ['3.5', 'Completed', 100, 'พัฒนาใบสั่งขาย ใบรับคืน ใบแจ้งหนี้ คลังหลายชั้น และบาร์โค้ดครบตามแผน'], ['3.9', 'In Progress', 25, 'เริ่มทบทวน Role Guard และการรวมศูนย์การตรวจสิทธิ์']], ['ISS-003', 'ISS-004', 'ISS-005'], [], { date: '2026-05-13', topic: 'ติดตามการเตรียมใช้งานจริง สคริปต์ติดตั้ง และการเริ่มโมดูลบัญชี' }],
|
['2026-05-08', '2026-04-30', '2026-05-08', 8, [['3.4', 'In Progress', 80, 'พัฒนาการเข้าสู่ระบบและสิทธิ์ตามบทบาทเกือบครบ เหลือขั้นตอนกู้คืนรหัสผ่าน'], ['3.5', 'Completed', 100, 'พัฒนาใบสั่งขาย ใบรับคืน ใบแจ้งหนี้ คลังหลายชั้น และบาร์โค้ดครบตามแผน'], ['3.9', 'In Progress', 25, 'เริ่มทบทวน Role Guard และการรวมศูนย์การตรวจสิทธิ์']], ['ISS-003', 'ISS-004', 'ISS-005'], [], { date: '2026-05-13', topic: 'ติดตามการเตรียมใช้งานจริง สคริปต์ติดตั้ง และการเริ่มโมดูลบัญชี' }],
|
||||||
['2026-05-13', '2026-05-11', '2026-05-13', 9, [['3.4', 'Completed', 100, 'พัฒนาการเข้าสู่ระบบ Onboarding และกู้คืนรหัสผ่านครบถ้วน'], ['3.6', 'Completed', 100, 'จัดทำ Base URL อัตโนมัติ สคริปต์ติดตั้งฐานข้อมูล และข้อมูลทดสอบ'], ['3.7', 'In Progress', 20, 'เริ่มพัฒนาโมดูลบัญชีและผังบัญชี']], ['ISS-006', 'ISS-007', 'ISS-008', 'ISS-009'], [], { date: '2026-05-23', topic: 'ติดตามการพัฒนาบัญชี การเงิน และการควบคุมสิทธิ์ผู้ใช้' }],
|
['2026-05-13', '2026-05-11', '2026-05-13', 9, [['3.4', 'Completed', 100, 'พัฒนาการเข้าสู่ระบบ Onboarding และกู้คืนรหัสผ่านครบถ้วน'], ['3.6', 'Completed', 100, 'จัดทำ Base URL อัตโนมัติ สคริปต์ติดตั้งฐานข้อมูล และข้อมูลทดสอบ'], ['3.7', 'In Progress', 20, 'เริ่มพัฒนาโมดูลบัญชีและผังบัญชี']], ['ISS-006', 'ISS-007', 'ISS-008', 'ISS-009'], [], { date: '2026-05-23', topic: 'ติดตามการพัฒนาบัญชี การเงิน และการควบคุมสิทธิ์ผู้ใช้' }],
|
||||||
['2026-05-23', '2026-05-20', '2026-05-23', 10, [['3.7', 'Completed', 100, 'พัฒนาผังบัญชี GL สมุดรายวัน รายงานบัญชี ใบวางบิลและใบเสร็จครบตามแผน'], ['3.8', 'In Progress', 45, 'อยู่ระหว่างพัฒนา Socket.IO และตารางสรุปยอด'], ['3.9', 'In Progress', 60, 'เพิ่มการเชิญผู้ใช้ การควบคุมสิทธิ์แอปพลิเคชัน และโควตารายการ']], ['ISS-010', 'ISS-011', 'ISS-012', 'ISS-013', 'ISS-014', 'ISS-015'], [], { date: '2026-05-29', topic: 'สรุปผลการพัฒนาและตั้ง Baseline การพัฒนา' }],
|
['2026-05-23', '2026-05-20', '2026-05-23', 10, [['3.7', 'Completed', 100, 'พัฒนาผังบัญชี GL สมุดรายวัน รายงานบัญชี ใบวางบิลและใบเสร็จครบตามแผน'], ['3.8', 'In Progress', 45, 'อยู่ระหว่างพัฒนา Socket.IO และตารางสรุปยอด'], ['3.9', 'In Progress', 60, 'เพิ่มการเชิญผู้ใช้ การควบคุมสิทธิ์แอปพลิเคชัน และโควตารายการ']], ['ISS-010', 'ISS-011', 'ISS-012', 'ISS-013', 'ISS-014', 'ISS-015'], [], { date: '2026-05-29', topic: 'สรุปผลการพัฒนาและตั้ง Baseline การพัฒนา' }],
|
||||||
['2026-05-29', '2026-05-24', '2026-05-29', 11, [['3.8', 'Completed', 100, 'พัฒนาบริการแจ้งเตือน Real-time ตารางสรุปยอด และงานตามกำหนดเวลาครบถ้วน'], ['3.9', 'Completed', 100, 'ทบทวนความปลอดภัย ขอบเขตบริษัท วงจรเอกสาร และปิดข้อบกพร่องที่พบ'], ['3.10', 'Completed', 100, 'ลดความซ้ำซ้อนของคลาสและตั้ง Baseline การพัฒนาเมื่อ 29 พ.ค. 69']], ['ISS-016', 'ISS-017', 'ISS-018', 'ISS-019', 'ISS-020', 'ISS-021', 'ISS-022', 'ISS-023', 'ISS-024', 'ISS-025', 'ISS-026', 'ISS-027'], ['CH-001'], { date: '2026-08-03', topic: 'ติดตามการตรวจสอบเอกสาร การจัดทำคู่มือ และการแก้ไขหลัง Baseline' }],
|
['2026-05-29', '2026-05-24', '2026-05-29', 11, [['3.8', 'Completed', 100, 'พัฒนาบริการแจ้งเตือน Real-time ตารางสรุปยอด และงานตามกำหนดเวลาครบถ้วน'], ['3.9', 'Completed', 100, 'ทบทวนความปลอดภัย ขอบเขตบริษัท วงจรเอกสาร และปิดข้อบกพร่องที่พบ'], ['3.10', 'Completed', 100, 'ลดความซ้ำซ้อนของคลาสและตั้ง Baseline การพัฒนาเมื่อ 29 พ.ค. 69']], ['ISS-016', 'ISS-017', 'ISS-018', 'ISS-019', 'ISS-020', 'ISS-021', 'ISS-022', 'ISS-023', 'ISS-024', 'ISS-025', 'ISS-026', 'ISS-027'], [], { date: '2026-08-03', topic: 'ติดตามการตรวจสอบเอกสาร การจัดทำคู่มือ และการแก้ไขหลัง Baseline' }],
|
||||||
['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-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 12 สถานการณ์ ผ่านทั้งหมดระหว่าง 10–14 ส.ค. 69'], ['4.7', 'In Progress', 60, 'จัดทำข้อมูลสาธิตเสร็จ อยู่ระหว่างปรับแบรนด์และชุดติดตั้ง Docker Compose'], ['5.1', 'In Progress', 70, 'ตรวจสอบความครบถ้วนของ Work Products']], [], ['CH-002'], { date: '2026-08-17', topic: 'สรุปผลการตรวจสอบรอบสุดท้าย และตรวจรับส่งมอบระบบ' }],
|
['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 เหลือการอบรมและปิดโครงการ']], [], ['CH-003'], { date: '2026-08-24', 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],
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,7 +171,7 @@ export const MEETINGS = [
|
|||||||
progress: [['Phase 1: Project Initiation', 'เสร็จสมบูรณ์', 100, 'อนุมัติ Project Charter และขอบเขตงานครบถ้วน'], ['Phase 2: Project Planning', 'กำลังดำเนินการ', 30, 'อยู่ระหว่างเก็บความต้องการลูกค้า'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 12, 'เข้าสู่ขั้นตอนวางแผนโครงการตามกำหนด']],
|
progress: [['Phase 1: Project Initiation', 'เสร็จสมบูรณ์', 100, 'อนุมัติ Project Charter และขอบเขตงานครบถ้วน'], ['Phase 2: Project Planning', 'กำลังดำเนินการ', 30, 'อยู่ระหว่างเก็บความต้องการลูกค้า'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 12, 'เข้าสู่ขั้นตอนวางแผนโครงการตามกำหนด']],
|
||||||
agenda: [['พิจารณาอนุมัติกฎบัตรโครงการ (Project Charter)', 'ยืนยันวัตถุประสงค์ ขอบเขต และงบประมาณ'], ['ทบทวน Stakeholder Register', 'ยืนยันบทบาทและระดับการมีส่วนร่วม'], ['แผนการเก็บความต้องการลูกค้า', 'กำหนดผู้ให้ข้อมูลและกรอบเวลา']],
|
agenda: [['พิจารณาอนุมัติกฎบัตรโครงการ (Project Charter)', 'ยืนยันวัตถุประสงค์ ขอบเขต และงบประมาณ'], ['ทบทวน Stakeholder Register', 'ยืนยันบทบาทและระดับการมีส่วนร่วม'], ['แผนการเก็บความต้องการลูกค้า', 'กำหนดผู้ให้ข้อมูลและกรอบเวลา']],
|
||||||
discussion: [['อนุมัติกฎบัตรโครงการ', 'Project Sponsor อนุมัติวัตถุประสงค์ ขอบเขต 11 ระบบงาน และเกณฑ์ความสำเร็จ', ['SeV'], 'อนุมัติ'], ['ผู้มีส่วนได้ส่วนเสีย', 'ยืนยันผู้รับผิดชอบตาม RACI ครบทุกบทบาท', ['ApS', 'YaB'], 'ยืนยันแล้ว'], ['การเก็บความต้องการ', 'มอบหมาย System Analyst สัมภาษณ์ฝ่ายคลังสินค้า ขาย จัดซื้อ และบัญชี', ['NoC'], 'ดำเนินการ']],
|
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: 'สรุปความต้องการลูกค้าและทบทวนร่างแผนโครงการ' },
|
next: { date: '2026-02-06', topic: 'สรุปความต้องการลูกค้าและทบทวนร่างแผนโครงการ' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -201,11 +212,11 @@ export const MEETINGS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: '2026-05-23', phase: 'Project Execution', taskId: '3.7', taskName: 'Accounting & Finance Workflows', time: '09:00 – 11:00 น.',
|
date: '2026-05-23', phase: 'Project Execution', taskId: '3.7', taskName: 'Accounting & Finance Workflows', time: '09:00 – 11:00 น.',
|
||||||
attachment: 'Progress Status Record 23 พ.ค. 69, Change Report CH-001',
|
attachment: 'Progress Status Record 23 พ.ค. 69',
|
||||||
progress: [['Phase 3: Project Execution', 'กำลังดำเนินการ', 85, 'บัญชีและการเงินเสร็จ เหลือบริการแจ้งเตือนและการทบทวนความปลอดภัย'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 72, 'เตรียมเข้าสู่ Baseline การพัฒนา']],
|
progress: [['Phase 3: Project Execution', 'กำลังดำเนินการ', 85, 'บัญชีและการเงินเสร็จ เหลือบริการแจ้งเตือนและการทบทวนความปลอดภัย'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 72, 'เตรียมเข้าสู่ Baseline การพัฒนา']],
|
||||||
agenda: [['ผลการพัฒนาบัญชีและการเงิน', 'ตรวจสอบความครบถ้วนตาม CR01:015–CR01:017'], ['พิจารณาคำขอเปลี่ยนแปลง CH-001 (Rack เป็น Bin)', 'ประเมินผลกระทบและอนุมัติ'], ['แผนงานก่อนตั้ง Baseline การพัฒนา', 'ยืนยันงานที่ต้องเสร็จภายใน 29 พ.ค. 69']],
|
agenda: [['ผลการพัฒนาบัญชีและการเงิน', 'ตรวจสอบความครบถ้วนตาม CR01:015–CR01:017'], ['คำเรียกตำแหน่งจัดเก็บ (Rack / Bin)', 'กำหนดคำเรียกที่ใช้ในหน้าจอให้ตรงกับการปฏิบัติงานจริง'], ['แผนงานก่อนตั้ง Baseline การพัฒนา', 'ยืนยันงานที่ต้องเสร็จภายใน 29 พ.ค. 69']],
|
||||||
discussion: [['บัญชีและการเงิน', 'พัฒนาผังบัญชี GL สมุดรายวัน รายงานบัญชี ใบวางบิลและใบเสร็จครบตามความต้องการ', ['ThS', 'NoC'], 'เสร็จสิ้น'], ['คำขอเปลี่ยนแปลง CH-001', 'เปลี่ยนคำเรียกตำแหน่งจัดเก็บจาก Rack เป็น Bin ให้ตรงกับคำที่พนักงานคลังใช้จริง ประเมินผลกระทบระดับ Medium ไม่กระทบกำหนดส่งมอบ', ['SeV', 'ApS', 'NoC'], 'อนุมัติ'], ['ความปลอดภัยและโควตารายการ', 'เพิ่มการควบคุมสิทธิ์แอปพลิเคชันและโควตารายการต่อบริษัท', ['ThS'], 'ดำเนินการ']],
|
discussion: [['บัญชีและการเงิน', 'พัฒนาผังบัญชี GL สมุดรายวัน รายงานบัญชี ใบวางบิลและใบเสร็จครบตามความต้องการ', ['ThS', 'NoC'], 'เสร็จสิ้น'], ['คำเรียกตำแหน่งจัดเก็บ', 'ที่ประชุมมีมติให้ใช้คำว่า Bin แทน Rack ทั้งระบบให้ตรงกับคำที่พนักงานคลังใช้จริง เป็นการปรับคำเรียกบนหน้าจอ ไม่เพิ่มหรือลดฟังก์ชันและไม่กระทบ Man-day จึงไม่ถือเป็นคำขอเปลี่ยนแปลง', ['SeV', 'ApS', 'NoC'], 'มติที่ประชุม'], ['ความปลอดภัยและโควตารายการ', 'เพิ่มการควบคุมสิทธิ์แอปพลิเคชันและโควตารายการต่อบริษัท', ['ThS'], 'ดำเนินการ']],
|
||||||
actions: [['ดำเนินการเปลี่ยนคำเรียกตามคำขอ CH-001', ['ThS'], '2026-05-27', 'Scheduled'], ['ทบทวนความปลอดภัยและวงจรเอกสารก่อนตั้ง Baseline (Task 3.9)', ['ThS', 'PaNg'], '2026-05-28', 'Scheduled'], ['บันทึกการแก้ไข ISS-010 ถึง ISS-015 ใน Correction Register', ['YaB'], '2026-05-25', 'Scheduled']],
|
actions: [['ปรับคำเรียก Rack เป็น Bin ตามมติที่ประชุม', ['ThS'], '2026-05-27', 'Scheduled'], ['ทบทวนความปลอดภัยและวงจรเอกสารก่อนตั้ง Baseline (Task 3.9)', ['ThS', 'PaNg'], '2026-05-28', 'Scheduled'], ['บันทึกการแก้ไข ISS-010 ถึง ISS-015 ใน Correction Register', ['YaB'], '2026-05-25', 'Scheduled']],
|
||||||
next: { date: '2026-05-29', topic: 'สรุปผลการพัฒนาและตั้ง Baseline การพัฒนา' },
|
next: { date: '2026-05-29', topic: 'สรุปผลการพัฒนาและตั้ง Baseline การพัฒนา' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -228,19 +239,19 @@ export const MEETINGS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: '2026-08-14', phase: 'Validation', taskId: '4.4', taskName: 'System Test & UAT', time: '09:00 – 11:00 น.',
|
date: '2026-08-14', phase: 'Validation', taskId: '4.4', taskName: 'System Test & UAT', time: '09:00 – 11:00 น.',
|
||||||
attachment: 'Test Report, Validation Results, Change Report CH-002',
|
attachment: 'Test Report, Validation Results',
|
||||||
progress: [['Phase 4: Verification & Validation', 'เสร็จสมบูรณ์', 100, 'ทดสอบ 45 Test Case และ UAT 12 สถานการณ์ ผ่านทั้งหมด'], ['Phase 5: Project Close', 'กำลังดำเนินการ', 60, 'อยู่ระหว่างตรวจสอบ Work Products และเตรียมชุดส่งมอบ'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 92, 'เตรียมตรวจรับและปิดโครงการ']],
|
progress: [['Phase 4: Verification & Validation', 'เสร็จสมบูรณ์', 100, `ทดสอบ 45 Test Case และ UAT ครบ 14 หมวดความต้องการ (${CRS.length} รายการ) ผ่านทั้งหมด`], ['Phase 5: Project Close', 'กำลังดำเนินการ', 60, 'อยู่ระหว่างตรวจสอบ Work Products และเตรียมชุดส่งมอบ'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 92, 'เตรียมตรวจรับและปิดโครงการ']],
|
||||||
agenda: [['สรุปผลการทดสอบระบบ 45 Test Case', 'ยืนยันผลการทดสอบและข้อบกพร่องคงค้าง'], ['สรุปผลการทดสอบการยอมรับ (UAT) 12 สถานการณ์', 'ยืนยันความพร้อมใช้งานจริง'], ['พิจารณาคำขอเปลี่ยนแปลง CH-002 (ข้อมูลสาธิต)', 'ประเมินผลกระทบและอนุมัติ'], ['แผนงานปิดโครงการ', 'ยืนยันงานที่เหลือถึง 24 ส.ค. 69']],
|
agenda: [['สรุปผลการทดสอบระบบ 45 Test Case', 'ยืนยันผลการทดสอบและข้อบกพร่องคงค้าง'], ['สรุปผลการทดสอบการยอมรับ (UAT) ตาม Customer Requirements CR01–CR14', 'ยืนยันความพร้อมใช้งานจริง'], ['ข้อมูลสาธิตสำหรับการตรวจรับ (Task 4.7)', 'ยืนยันความพร้อมของข้อมูลตั้งต้น'], ['แผนงานปิดโครงการ', 'ยืนยันงานที่เหลือถึง 24 ส.ค. 69']],
|
||||||
discussion: [['ผลการทดสอบระบบ', 'ทดสอบ 45 Test Case บน Internal Testing Server ผ่านทั้งหมด ไม่พบข้อบกพร่องคงค้าง', ['PaNg'], 'ผ่าน'], ['ผลการทดสอบการยอมรับ', 'ตัวแทนลูกค้าทดสอบ 12 สถานการณ์บนสภาพแวดล้อมใช้งานจริง ผ่านทั้งหมด', ['SeV'], 'ผ่าน'], ['คำขอเปลี่ยนแปลง CH-002', 'เพิ่มชุดข้อมูลสาธิตเพื่อใช้ตรวจรับรายงานและขั้นตอนการทำงาน ประเมินผลกระทบระดับ Medium', ['ApS', 'SeV'], 'อนุมัติ'], ['งานที่เหลือ', 'ตรวจสอบ Work Products รอบสุดท้าย เตรียมชุดติดตั้ง ตรวจรับ อบรม และปิดโครงการ', ['ApS', 'YaB'], 'ดำเนินการ']],
|
discussion: [['ผลการทดสอบระบบ', 'ทดสอบ 45 Test Case บน Internal Testing Server ผ่านทั้งหมด ไม่พบข้อบกพร่องคงค้าง', ['PaNg'], 'ผ่าน'], ['ผลการทดสอบการยอมรับ', `ตัวแทนลูกค้าทดสอบการยอมรับครบ 14 หมวดความต้องการ (${CRS.length} รายการ) บนสภาพแวดล้อมใช้งานจริง ผ่านทั้งหมด`, ['SeV'], 'ผ่าน'], ['ข้อมูลสาธิต', 'จัดเตรียมข้อมูลสาธิตสำหรับการตรวจรับรายงานและขั้นตอนการทำงานเสร็จแล้ว เป็นงานเตรียมการส่งมอบตาม Task 4.7 ไม่เปลี่ยนแปลงขอบเขตหรือสิ่งส่งมอบ', ['ApS', 'SeV'], 'เสร็จสิ้น'], ['งานที่เหลือ', 'ตรวจสอบ Work Products รอบสุดท้าย เตรียมชุดติดตั้ง ตรวจรับ อบรม และปิดโครงการ', ['ApS', 'YaB'], 'ดำเนินการ']],
|
||||||
actions: [['ตรวจสอบ Work Products รอบที่ 4 และจัดทำ List of Evidence', ['YaB', 'ApS'], '2026-08-17', 'Scheduled'], ['จัดทำชุดติดตั้ง Docker Compose และปรับแบรนด์ (CH-003)', ['ThS'], '2026-08-17', 'Scheduled'], ['นัดตรวจรับส่งมอบระบบกับ Project Sponsor', ['ApS'], '2026-08-17', 'Scheduled']],
|
actions: [['ตรวจสอบ Work Products รอบที่ 4 และจัดทำ List of Evidence', ['YaB', 'ApS'], '2026-08-17', 'Scheduled'], ['จัดทำชุดติดตั้ง Docker Compose (CR10:002) และปรับแบรนด์ (Task 4.7)', ['ThS'], '2026-08-17', 'Scheduled'], ['นัดตรวจรับส่งมอบระบบกับ Project Sponsor', ['ApS'], '2026-08-17', 'Scheduled']],
|
||||||
next: { date: '2026-08-17', topic: 'สรุปผลการตรวจสอบรอบสุดท้ายและตรวจรับส่งมอบระบบ' },
|
next: { date: '2026-08-17', topic: 'สรุปผลการตรวจสอบรอบสุดท้ายและตรวจรับส่งมอบระบบ' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: '2026-08-17', phase: 'Project Close', taskId: '5.3', taskName: 'Acceptance & Project Closure', time: '09:00 – 11:00 น.',
|
date: '2026-08-17', phase: 'Project Close', taskId: '5.3', taskName: 'Acceptance & Project Closure', time: '09:00 – 11:00 น.',
|
||||||
attachment: 'Acceptance Report, List of Evidence, Verification Results, Change Report CH-003',
|
attachment: 'Acceptance Report, List of Evidence, Verification Results',
|
||||||
progress: [['Phase 3: Project Execution', 'เสร็จสมบูรณ์', 100, 'พัฒนาระบบครบตามขอบเขต Baseline 6c39700'], ['Phase 4: Verification & Validation', 'เสร็จสมบูรณ์', 100, 'ตรวจสอบ ทดสอบ และ UAT ผ่านครบทุกรายการ'], ['Phase 5: Project Close', 'กำลังดำเนินการ', 80, 'ตรวจรับระบบแล้ว เหลือการอบรมและปิดงานควบคุมปฏิบัติการ'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 96, 'ตรวจรับระบบเรียบร้อย']],
|
progress: [['Phase 3: Project Execution', 'เสร็จสมบูรณ์', 100, 'พัฒนาระบบครบตามขอบเขต Baseline 6c39700'], ['Phase 4: Verification & Validation', 'เสร็จสมบูรณ์', 100, 'ตรวจสอบ ทดสอบ และ UAT ผ่านครบทุกรายการ'], ['Phase 5: Project Close', 'กำลังดำเนินการ', 80, 'ตรวจรับระบบแล้ว เหลือการอบรมและปิดงานควบคุมปฏิบัติการ'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 96, 'ตรวจรับระบบเรียบร้อย']],
|
||||||
agenda: [['นำเสนอผลการดำเนินโครงการและสิ่งส่งมอบ', 'สรุปผลงานทั้งหมดเทียบกับแผนและผลลัพธ์จริง'], ['ตรวจสอบความครบถ้วนของเอกสารและหลักฐาน', 'ยืนยันเอกสารครบตามมาตรฐาน ISO/IEC 29110'], ['พิจารณาคำขอเปลี่ยนแปลง CH-003', 'ประเมินผลกระทบและอนุมัติ'], ['พิจารณาตรวจรับส่งมอบระบบ', 'ลงมติผลการตรวจรับ']],
|
agenda: [['นำเสนอผลการดำเนินโครงการและสิ่งส่งมอบ', 'สรุปผลงานทั้งหมดเทียบกับแผนและผลลัพธ์จริง'], ['ตรวจสอบความครบถ้วนของเอกสารและหลักฐาน', 'ยืนยันเอกสารครบตามมาตรฐาน ISO/IEC 29110'], ['ชุดติดตั้งและอัตลักษณ์องค์กร (Task 4.7)', 'ยืนยันความพร้อมของชุดส่งมอบ'], ['พิจารณาตรวจรับส่งมอบระบบ', 'ลงมติผลการตรวจรับ']],
|
||||||
discussion: [['ผลการดำเนินโครงการ', 'พัฒนาระบบครบตามขอบเขต 11 ระบบงาน ทดสอบและ UAT ผ่านทั้งหมด ไม่มีข้อบกพร่องระดับวิกฤตคงค้าง', ['ApS', 'SeV'], 'ผ่านการรับรอง'], ['ความครบถ้วนของเอกสาร', 'ตรวจสอบ Work Products รอบที่ 4 ครบทุกรายการ ผลผ่านทั้งหมด และจัดทำ List of Evidence เรียบร้อย', ['YaB', 'PaNg'], 'สมบูรณ์'], ['คำขอเปลี่ยนแปลง CH-003', 'ปรับอัตลักษณ์องค์กรและเพิ่มชุดติดตั้ง Docker Compose ประเมินผลกระทบระดับ Medium', ['SeV', 'ApS'], 'อนุมัติ'], ['ผลการตรวจรับ', 'Project Sponsor ตรวจรับสิ่งส่งมอบทั้งหมด ผลการตรวจรับ Accepted', ['SeV'], 'ตรวจรับแล้ว']],
|
discussion: [['ผลการดำเนินโครงการ', 'พัฒนาระบบครบตามขอบเขต 11 ระบบงาน ทดสอบและ UAT ผ่านทั้งหมด ไม่มีข้อบกพร่องระดับวิกฤตคงค้าง', ['ApS', 'SeV'], 'ผ่านการรับรอง'], ['ความครบถ้วนของเอกสาร', 'ตรวจสอบ Work Products รอบที่ 4 ครบทุกรายการ ผลผ่านทั้งหมด และจัดทำ List of Evidence เรียบร้อย', ['YaB', 'PaNg'], 'สมบูรณ์'], ['ชุดติดตั้งและอัตลักษณ์องค์กร', 'จัดทำชุดติดตั้ง Docker Compose ตามความต้องการ CR10:002 และปรับอัตลักษณ์องค์กรในหน้าจอและเอกสารเสร็จแล้ว เป็นงานเตรียมการส่งมอบตาม Task 4.7 ไม่เปลี่ยนแปลงขอบเขตหรือสิ่งส่งมอบ', ['SeV', 'ApS'], 'เสร็จสิ้น'], ['ผลการตรวจรับ', 'Project Sponsor ตรวจรับสิ่งส่งมอบทั้งหมด ผลการตรวจรับ Accepted', ['SeV'], 'ตรวจรับแล้ว']],
|
||||||
actions: [['จัดอบรมผู้ใช้งานก่อนเปิดใช้งานจริง (Task 5.2)', ['ThS', 'PaNg'], '2026-08-22', 'Scheduled'], ['ปิดงานควบคุมการสำรองข้อมูลและการเฝ้าระวังระบบ', ['ThS'], '2026-08-23', 'Scheduled'], ['ปิดโครงการและส่งมอบเอกสารเข้าระบบควบคุมเอกสาร', ['ApS', 'YaB'], '2026-08-24', 'Scheduled']],
|
actions: [['จัดอบรมผู้ใช้งานก่อนเปิดใช้งานจริง (Task 5.2)', ['ThS', 'PaNg'], '2026-08-22', 'Scheduled'], ['ปิดงานควบคุมการสำรองข้อมูลและการเฝ้าระวังระบบ', ['ThS'], '2026-08-23', 'Scheduled'], ['ปิดโครงการและส่งมอบเอกสารเข้าระบบควบคุมเอกสาร', ['ApS', 'YaB'], '2026-08-24', 'Scheduled']],
|
||||||
next: { date: '2026-08-24', topic: 'สรุปผลการอบรม การปิดงานควบคุมปฏิบัติการ และปิดโครงการอย่างเป็นทางการ' },
|
next: { date: '2026-08-24', topic: 'สรุปผลการอบรม การปิดงานควบคุมปฏิบัติการ และปิดโครงการอย่างเป็นทางการ' },
|
||||||
},
|
},
|
||||||
@@ -249,8 +260,15 @@ export const MEETINGS = [
|
|||||||
attachment: 'Training Report, Product Operation Guide, Project Repository (Backup)',
|
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, 'โครงการเสร็จสมบูรณ์ ปิดโครงการอย่างเป็นทางการ']],
|
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 และเอกสารโครงการ', 'ยืนยันการสำรองและกู้คืนได้จริง'], ['ปิดโครงการอย่างเป็นทางการ', 'ลงมติปิดโครงการและส่งมอบเอกสาร']],
|
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']],
|
actions: [['จัดเก็บเอกสารโครงการทั้งหมดเข้าระบบควบคุมเอกสาร', ['YaB'], '2026-08-24', 'เสร็จสมบูรณ์'], ['ติดตามการตรวจนับสต๊อกสิ้นเดือนแรกหลังเปิดใช้งานจริง', ['ApS'], '2026-09-30', 'Scheduled']],
|
||||||
next: null,
|
next: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Sanity: every correction is drawn into exactly one lesson.
|
||||||
|
{
|
||||||
|
const drawn = LESSONS.flatMap(([, issueIds]) => issueIds);
|
||||||
|
const missing = CORRECTIONS.map(([id]) => id).filter((id) => !drawn.includes(id));
|
||||||
|
if (missing.length || drawn.length !== new Set(drawn).size) throw new Error(`Lessons must cover each correction once; missing: ${missing.join(', ')}`);
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,11 +38,11 @@ export const WPS = [
|
|||||||
['WP 2.0', `เอกสาร ${CODE} Customer Requirements`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
['WP 2.0', `เอกสาร ${CODE} Customer Requirements`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
||||||
['WP 3.0', `เอกสาร ${CODE} Software Requirements Specification`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
['WP 3.0', `เอกสาร ${CODE} Software Requirements Specification`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
||||||
['WP 4.0', `เอกสาร ${CODE} Software Design`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
['WP 4.0', `เอกสาร ${CODE} Software Design`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
||||||
['WP 5.0', `เอกสาร ${CODE} Change Report`, 'ส่งเอกสารจำนวน 3 ฉบับ (CH-001–CH-003)'],
|
['WP 5.0', `เอกสาร ${CODE} Change Report`, 'ส่งเอกสารจำนวน 1 ชุด (ทะเบียนการควบคุมการเปลี่ยนแปลง)'],
|
||||||
['WP 6.0', `เอกสาร ${CODE} Test Case and Test Procedures`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
['WP 6.0', `เอกสาร ${CODE} Test Case and Test Procedures`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
||||||
['WP 7.0', `เอกสาร ${CODE} Validation Results`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
['WP 7.0', `เอกสาร ${CODE} Validation Results`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
||||||
['WP 8.0', `เอกสาร ${CODE} Software User Document`, 'ส่งเอกสารจำนวน 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 10.0', `เอกสาร ${CODE} Product Operation Guide`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
||||||
['WP 11.0', `เอกสาร ${CODE} Maintenance Document`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
['WP 11.0', `เอกสาร ${CODE} Maintenance Document`, 'ส่งเอกสารจำนวน 1 ชุด'],
|
||||||
];
|
];
|
||||||
@@ -62,118 +62,115 @@ export const SCOPE = [
|
|||||||
['ระบบติดตั้งและตั้งค่า', ['ติดตั้งแบบ Manual ผ่าน setup.php', 'ติดตั้งแบบ Container ผ่าน Docker Compose (php-apache, mariadb, node/pm2)', 'สร้างค่าตั้งค่าและความลับของระบบจาก .env โดยไม่เก็บใน Source Control']],
|
['ระบบติดตั้งและตั้งค่า', ['ติดตั้งแบบ Manual ผ่าน setup.php', 'ติดตั้งแบบ Container ผ่าน Docker Compose (php-apache, mariadb, node/pm2)', 'สร้างค่าตั้งค่าและความลับของระบบจาก .env โดยไม่เก็บใน Source Control']],
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/** Customer Requirements (CR01–CR14). Each: [id, text]. */
|
||||||
* Customer Requirements (CR01–CR14). Each: [id, text, remark].
|
|
||||||
* Remark keeps the original FR-/NFR- identifier that appeared in earlier drafts so cross-references stay valid.
|
|
||||||
*/
|
|
||||||
export const CR_GROUPS = [
|
export const CR_GROUPS = [
|
||||||
['CR01', 'Feature & Functional Characteristics', [
|
['CR01', 'Feature & Functional Characteristics', [
|
||||||
['CR01:001', 'ระบบต้องรองรับการลงทะเบียนเจ้าของบริษัทและการเชิญผู้ใช้งานเข้าร่วมบริษัท (Onboarding)', 'FR-001'],
|
['CR01:001', 'ระบบต้องรองรับการลงทะเบียนเจ้าของบริษัทและการเชิญผู้ใช้งานเข้าร่วมบริษัท (Onboarding)'],
|
||||||
['CR01:002', 'ระบบต้องยืนยันตัวตนผู้ใช้และบังคับสิทธิ์ตามบทบาท Owner, Admin, Staff และ Viewer', 'FR-002'],
|
['CR01:002', 'ระบบต้องยืนยันตัวตนผู้ใช้และบังคับสิทธิ์ตามบทบาท Owner, Admin, Staff และ Viewer'],
|
||||||
['CR01:003', 'ระบบต้องรองรับการกู้คืนรหัสผ่าน การควบคุม Session และการยืนยัน OTP ตามที่กำหนด', 'FR-003'],
|
['CR01:003', 'ระบบต้องรองรับการกู้คืนรหัสผ่าน การควบคุม Session และการยืนยัน OTP ตามที่กำหนด'],
|
||||||
['CR01:004', 'ระบบต้องให้ผู้ดูแลจัดการข้อมูลบริษัท, SMTP, การตั้งค่าระบบ, ผู้ใช้งาน และสิทธิ์การเข้าถึงแอปพลิเคชัน', 'FR-004'],
|
['CR01:004', 'ระบบต้องให้ผู้ดูแลจัดการข้อมูลบริษัท, SMTP, การตั้งค่าระบบ, ผู้ใช้งาน และสิทธิ์การเข้าถึงแอปพลิเคชัน'],
|
||||||
['CR01:005', 'ระบบต้องจัดการข้อมูลคลังสินค้า, พื้นที่/ช่องจัดเก็บ, หมวดสินค้า, สินค้า, ประเภทผู้ติดต่อ และผู้ติดต่อ', 'FR-005'],
|
['CR01:005', 'ระบบต้องจัดการข้อมูลคลังสินค้า, พื้นที่/ช่องจัดเก็บ, หมวดสินค้า, สินค้า, ประเภทผู้ติดต่อ และผู้ติดต่อ'],
|
||||||
['CR01:006', 'ระบบต้องรองรับโครงสร้างตำแหน่งจัดเก็บทั้งแบบคลังเดียวและแบบหลายชั้น (คลัง/พื้นที่/ช่อง)', 'FR-006'],
|
['CR01:006', 'ระบบต้องรองรับโครงสร้างตำแหน่งจัดเก็บทั้งแบบคลังเดียวและแบบหลายชั้น (คลัง/พื้นที่/ช่อง)'],
|
||||||
['CR01:007', 'ระบบต้องบันทึกการรับสินค้าเข้า (Stock-in) ระบุสินค้า จำนวน ตำแหน่ง เอกสารอ้างอิง และข้อมูลติดตาม', 'FR-007'],
|
['CR01:007', 'ระบบต้องบันทึกการรับสินค้าเข้า (Stock-in) ระบุสินค้า จำนวน ตำแหน่ง เอกสารอ้างอิง และข้อมูลติดตาม'],
|
||||||
['CR01:008', 'ระบบต้องบันทึกการจ่ายสินค้าออก (Stock-out) โดยตรวจสอบสิทธิ์และยอดคงเหลือก่อนจ่าย', 'FR-008'],
|
['CR01:008', 'ระบบต้องบันทึกการจ่ายสินค้าออก (Stock-out) โดยตรวจสอบสิทธิ์และยอดคงเหลือก่อนจ่าย'],
|
||||||
['CR01:009', 'ระบบต้องโอนย้ายสินค้าระหว่างตำแหน่งจัดเก็บที่ได้รับอนุญาตโดยยอดต้นทาง/ปลายทางสมดุลกัน', 'FR-009'],
|
['CR01:009', 'ระบบต้องโอนย้ายสินค้าระหว่างตำแหน่งจัดเก็บที่ได้รับอนุญาตโดยยอดต้นทาง/ปลายทางสมดุลกัน'],
|
||||||
['CR01:010', 'ระบบต้องติดตาม Lot, Serial Number และวันหมดอายุของสินค้าที่เกี่ยวข้อง', 'FR-010'],
|
['CR01:010', 'ระบบต้องติดตาม Lot, Serial Number และวันหมดอายุของสินค้าที่เกี่ยวข้อง'],
|
||||||
['CR01:011', 'ระบบต้องแสดงภาพรวมสต๊อก, ประวัติความเคลื่อนไหว, ความจุ/การใช้พื้นที่, สินค้าใกล้หมด, สินค้าหมดอายุ และข้อมูล Lot', 'FR-011'],
|
['CR01:011', 'ระบบต้องแสดงภาพรวมสต๊อก, ประวัติความเคลื่อนไหว, ความจุ/การใช้พื้นที่, สินค้าใกล้หมด, สินค้าหมดอายุ และข้อมูล Lot'],
|
||||||
['CR01:012', 'ระบบต้องพิมพ์บาร์โค้ดสินค้า (SKU) และตำแหน่งจัดเก็บ และรองรับการสแกนในหน้าจอที่กำหนด', 'FR-012'],
|
['CR01:012', 'ระบบต้องพิมพ์บาร์โค้ดสินค้า (SKU) และตำแหน่งจัดเก็บ และรองรับการสแกนในหน้าจอที่กำหนด'],
|
||||||
['CR01:013', 'ระบบต้องสร้างและจัดการใบเสนอราคา, ใบสั่งขาย, ใบแจ้งหนี้, ใบรับคืน และใบลดหนี้', 'FR-013'],
|
['CR01:013', 'ระบบต้องสร้างและจัดการใบเสนอราคา, ใบสั่งขาย, ใบแจ้งหนี้, ใบรับคืน และใบลดหนี้'],
|
||||||
['CR01:014', 'ระบบต้องสร้างและจัดการใบขอซื้อ, ใบสั่งซื้อ, ใบแจ้งหนี้ซื้อ และใบคืนสินค้าผู้ขาย', 'FR-014'],
|
['CR01:014', 'ระบบต้องสร้างและจัดการใบขอซื้อ, ใบสั่งซื้อ, ใบแจ้งหนี้ซื้อ และใบคืนสินค้าผู้ขาย'],
|
||||||
['CR01:015', 'ระบบต้องสร้างและจัดการใบวางบิลรับ, ใบเสร็จรับเงิน, ใบวางบิลจ่าย และใบสำคัญจ่าย', 'FR-015'],
|
['CR01:015', 'ระบบต้องสร้างและจัดการใบวางบิลรับ, ใบเสร็จรับเงิน, ใบวางบิลจ่าย และใบสำคัญจ่าย'],
|
||||||
['CR01:016', 'ระบบต้องจัดการผังบัญชี, แผนก, สูตรบัญชี, สมุดรายวัน และบัญชีแยกประเภท', 'FR-016'],
|
['CR01:016', 'ระบบต้องจัดการผังบัญชี, แผนก, สูตรบัญชี, สมุดรายวัน และบัญชีแยกประเภท'],
|
||||||
['CR01:017', 'ระบบต้องจัดทำรายงานงบทดลอง, งบกำไรขาดทุน, งบดุล, ภาษีมูลค่าเพิ่ม, สมุดรายวัน และความเคลื่อนไหว GL', 'FR-017'],
|
['CR01:017', 'ระบบต้องจัดทำรายงานงบทดลอง, งบกำไรขาดทุน, งบดุล, ภาษีมูลค่าเพิ่ม, สมุดรายวัน และความเคลื่อนไหว GL'],
|
||||||
['CR01:018', 'ระบบต้องออกเลขที่เอกสารอัตโนมัติและควบคุมสถานะ/วงจรชีวิตของเอกสาร', 'FR-018'],
|
['CR01:018', 'ระบบต้องออกเลขที่เอกสารอัตโนมัติและควบคุมสถานะ/วงจรชีวิตของเอกสาร'],
|
||||||
['CR01:019', 'ระบบต้องรองรับการแนบไฟล์ที่อนุญาตกับรายการที่กำหนด', 'FR-019'],
|
['CR01:019', 'ระบบต้องรองรับการแนบไฟล์ที่อนุญาตกับรายการที่กำหนด'],
|
||||||
['CR01:020', 'ระบบต้องให้ผู้ใช้กรอง ดู พิมพ์ และส่งออกรายงานปฏิบัติการและรายงานผู้บริหาร', 'FR-020'],
|
['CR01:020', 'ระบบต้องให้ผู้ใช้กรอง ดู พิมพ์ และส่งออกรายงานปฏิบัติการและรายงานผู้บริหาร'],
|
||||||
['CR01:021', 'ระบบต้องแจ้งเตือนผู้ใช้ที่เกี่ยวข้องเมื่อสถานะเอกสารเปลี่ยนหรือมีเหตุการณ์ปฏิบัติการ', 'FR-021'],
|
['CR01:021', 'ระบบต้องแจ้งเตือนผู้ใช้ที่เกี่ยวข้องเมื่อสถานะเอกสารเปลี่ยนหรือมีเหตุการณ์ปฏิบัติการ'],
|
||||||
['CR01:022', 'ระบบต้องสรุปยอดสต๊อก/GL และแจ้งเตือนสินค้าใกล้หมดและใบแจ้งหนี้ค้างชำระตามกำหนดเวลา', 'FR-022'],
|
['CR01:022', 'ระบบต้องสรุปยอดสต๊อก/GL และแจ้งเตือนสินค้าใกล้หมดและใบแจ้งหนี้ค้างชำระตามกำหนดเวลา'],
|
||||||
['CR01:023', 'ระบบต้องเก็บผู้สร้าง ผู้แก้ไข สถานะ และประวัติรายการเพื่อการตรวจสอบ', 'FR-023'],
|
['CR01:023', 'ระบบต้องเก็บผู้สร้าง ผู้แก้ไข สถานะ และประวัติรายการเพื่อการตรวจสอบ'],
|
||||||
['CR01:024', 'ระบบต้องจำกัดข้อมูลบริษัทและคลังสินค้าให้เฉพาะผู้ใช้ที่ได้รับอนุญาตในบริบทปัจจุบัน', 'FR-024'],
|
['CR01:024', 'ระบบต้องจำกัดข้อมูลบริษัทและคลังสินค้าให้เฉพาะผู้ใช้ที่ได้รับอนุญาตในบริบทปัจจุบัน'],
|
||||||
]],
|
]],
|
||||||
['CR02', 'Performance Considerations', [
|
['CR02', 'Performance Considerations', [
|
||||||
['CR02:001', 'ระบบต้องตอบสนองงานประจำวัน (เปิดหน้าจอ, ค้นหา, สร้างเอกสาร) ภายในเวลาที่ใช้งานได้จริงบนสภาพแวดล้อมที่ตกลง', 'NFR-006'],
|
['CR02:001', 'ระบบต้องตอบสนองงานประจำวัน (เปิดหน้าจอ, ค้นหา, สร้างเอกสาร) ภายในเวลาที่ใช้งานได้จริงบนสภาพแวดล้อมที่ตกลง'],
|
||||||
['CR02:002', 'ระบบต้องมีตารางสรุปยอด (Aggregate) เพื่อให้แดชบอร์ดและรายงานแสดงผลได้โดยไม่ต้องคำนวณใหม่ทุกครั้ง', 'NFR-006'],
|
['CR02:002', 'ระบบต้องมีตารางสรุปยอด (Aggregate) เพื่อให้แดชบอร์ดและรายงานแสดงผลได้โดยไม่ต้องคำนวณใหม่ทุกครั้ง'],
|
||||||
['CR02:003', 'ระบบต้องรองรับปริมาณข้อมูลและผู้ใช้พร้อมกันในระดับที่ตกลงสำหรับสภาพแวดล้อมใช้งานจริง', 'NFR-006'],
|
['CR02:003', 'ระบบต้องรองรับปริมาณข้อมูลและผู้ใช้พร้อมกันในระดับที่ตกลงสำหรับสภาพแวดล้อมใช้งานจริง'],
|
||||||
]],
|
]],
|
||||||
['CR03', 'Interface Considerations', [
|
['CR03', 'Interface Considerations', [
|
||||||
['CR03:001', 'ระบบต้องเชื่อมต่อฐานข้อมูล MySQL/MariaDB 2 ฐาน (wms สำหรับผู้ใช้/บริษัท และ wms2 สำหรับคลัง/บัญชี)', 'Operational context'],
|
['CR03:001', 'ระบบต้องเชื่อมต่อฐานข้อมูล MySQL/MariaDB 2 ฐาน (wms สำหรับผู้ใช้/บริษัท และ wms2 สำหรับคลัง/บัญชี)'],
|
||||||
['CR03:002', 'ระบบต้องใช้งานผ่าน Web Browser มาตรฐาน (Chrome, Edge, Firefox) ได้', 'NFR-008'],
|
['CR03:002', 'ระบบต้องใช้งานผ่าน Web Browser มาตรฐาน (Chrome, Edge, Firefox) ได้'],
|
||||||
['CR03:003', 'ระบบต้องส่งเหตุการณ์ไปยังบริการ Node.js ผ่าน Endpoint ภายในที่ป้องกันด้วย Secret', 'NFR-002'],
|
['CR03:003', 'ระบบต้องส่งเหตุการณ์ไปยังบริการ Node.js ผ่าน Endpoint ภายในที่ป้องกันด้วย Secret'],
|
||||||
['CR03:004', 'Browser ต้องเชื่อมต่อ Socket.IO Endpoint สาธารณะเพื่อรับการแจ้งเตือนแบบ Real-time', 'FR-021'],
|
['CR03:004', 'Browser ต้องเชื่อมต่อ Socket.IO Endpoint สาธารณะเพื่อรับการแจ้งเตือนแบบ Real-time'],
|
||||||
['CR03:005', 'ระบบต้องส่งอีเมล Onboarding, กู้คืนรหัสผ่าน และแจ้งเตือนผ่าน SMTP ที่ตั้งค่าต่อบริษัท', 'FR-004'],
|
['CR03:005', 'ระบบต้องส่งอีเมล Onboarding, กู้คืนรหัสผ่าน และแจ้งเตือนผ่าน SMTP ที่ตั้งค่าต่อบริษัท'],
|
||||||
]],
|
]],
|
||||||
['CR04', 'Required System Characteristics', [
|
['CR04', 'Required System Characteristics', [
|
||||||
['CR04:001', 'ระบบต้องพัฒนาบนสถาปัตยกรรม Web-based Application', 'Operational context'],
|
['CR04:001', 'ระบบต้องพัฒนาบนสถาปัตยกรรม Web-based Application'],
|
||||||
['CR04:002', 'ระบบต้องเก็บข้อมูลแบบ Relational Database และรักษา Referential Integrity', 'Data requirements'],
|
['CR04:002', 'ระบบต้องเก็บข้อมูลแบบ Relational Database และรักษา Referential Integrity'],
|
||||||
['CR04:003', 'ระบบต้องรองรับการเข้าสู่ระบบด้วย Username/Password และกำหนดบทบาทผู้ใช้', 'FR-002'],
|
['CR04:003', 'ระบบต้องรองรับการเข้าสู่ระบบด้วย Username/Password และกำหนดบทบาทผู้ใช้'],
|
||||||
['CR04:004', 'ระบบต้องทำงานบน PHP 8 ขึ้นไป, MySQL/MariaDB และ Node.js', 'NFR-008'],
|
['CR04:004', 'ระบบต้องทำงานบน PHP 8 ขึ้นไป, MySQL/MariaDB และ Node.js'],
|
||||||
]],
|
]],
|
||||||
['CR05', 'Human Engineering Considerations', [
|
['CR05', 'Human Engineering Considerations', [
|
||||||
['CR05:001', 'UI ต้องเป็น Responsive ใช้งานได้ทั้งบน Desktop และอุปกรณ์หน้าคลังสินค้า (Tablet/Mobile)', 'NFR-005'],
|
['CR05:001', 'UI ต้องเป็น Responsive ใช้งานได้ทั้งบน Desktop และอุปกรณ์หน้าคลังสินค้า (Tablet/Mobile)'],
|
||||||
['CR05:002', 'เมนูและปุ่มคำสั่งต้องแสดงตามบทบาทและสิทธิ์การเข้าถึงของผู้ใช้', 'Interface requirements'],
|
['CR05:002', 'เมนูและปุ่มคำสั่งต้องแสดงตามบทบาทและสิทธิ์การเข้าถึงของผู้ใช้'],
|
||||||
['CR05:003', 'ระบบต้องแสดงผลการตรวจสอบข้อมูล สถานะ ความสำเร็จ และข้อผิดพลาดอย่างชัดเจน', 'Interface requirements'],
|
['CR05:003', 'ระบบต้องแสดงผลการตรวจสอบข้อมูล สถานะ ความสำเร็จ และข้อผิดพลาดอย่างชัดเจน'],
|
||||||
['CR05:004', 'ระบบต้องมีขั้นตอนยืนยันก่อนลบ ยกเลิก หรือทำรายการที่ย้อนกลับไม่ได้', 'Usability practice'],
|
['CR05:004', 'ระบบต้องมีขั้นตอนยืนยันก่อนลบ ยกเลิก หรือทำรายการที่ย้อนกลับไม่ได้'],
|
||||||
['CR05:005', 'ระบบต้องพิมพ์เอกสารธุรกิจและฉลากบาร์โค้ดในรูปแบบที่ใช้งานได้', 'Interface requirements'],
|
['CR05:005', 'ระบบต้องพิมพ์เอกสารธุรกิจและฉลากบาร์โค้ดในรูปแบบที่ใช้งานได้'],
|
||||||
]],
|
]],
|
||||||
['CR06', 'Security Considerations', [
|
['CR06', 'Security Considerations', [
|
||||||
['CR06:001', 'การเชื่อมต่อในสภาพแวดล้อมใช้งานจริงต้องเข้ารหัสด้วย HTTPS/TLS', 'NFR-001'],
|
['CR06:001', 'การเชื่อมต่อในสภาพแวดล้อมใช้งานจริงต้องเข้ารหัสด้วย HTTPS/TLS'],
|
||||||
['CR06:002', 'ค่าตั้งค่าและความลับของระบบต้องไม่ถูกเก็บใน Source Control และไม่เข้าถึงได้จากเว็บสาธารณะ', 'NFR-001'],
|
['CR06:002', 'ค่าตั้งค่าและความลับของระบบต้องไม่ถูกเก็บใน Source Control และไม่เข้าถึงได้จากเว็บสาธารณะ'],
|
||||||
['CR06:003', 'การทำงานฝั่งเซิร์ฟเวอร์ต้องตรวจสอบข้อมูลนำเข้า ยืนยันตัวตน ตรวจสิทธิ์ และจำกัดขอบเขตบริษัท/คลัง', 'NFR-002'],
|
['CR06:003', 'การทำงานฝั่งเซิร์ฟเวอร์ต้องตรวจสอบข้อมูลนำเข้า ยืนยันตัวตน ตรวจสิทธิ์ และจำกัดขอบเขตบริษัท/คลัง'],
|
||||||
['CR06:004', 'ระบบต้องจำกัดสิทธิ์การเข้าถึงตามบทบาท (Role-based Access Control) ทั้งใน UI และฝั่งเซิร์ฟเวอร์', 'FR-002'],
|
['CR06:004', 'ระบบต้องจำกัดสิทธิ์การเข้าถึงตามบทบาท (Role-based Access Control) ทั้งใน UI และฝั่งเซิร์ฟเวอร์'],
|
||||||
['CR06:005', 'ระบบต้องป้องกัน SQL Injection และ Cross-Site Scripting', 'NFR-002'],
|
['CR06:005', 'ระบบต้องป้องกัน SQL Injection และ Cross-Site Scripting'],
|
||||||
['CR06:006', 'ระบบต้องบล็อกการเข้าสู่ระบบซ้ำซ้อน (Concurrent Login) ของบัญชีเดียวกัน', 'FR-003'],
|
['CR06:006', 'ระบบต้องบล็อกการเข้าสู่ระบบซ้ำซ้อน (Concurrent Login) ของบัญชีเดียวกัน'],
|
||||||
]],
|
]],
|
||||||
['CR07', 'Environmental Considerations', [
|
['CR07', 'Environmental Considerations', [
|
||||||
['CR07:001', 'ระบบต้องทำงานบน Linux Server ในรูปแบบติดตั้งเอง (LAMP) หรือ Docker Compose', 'NFR-004'],
|
['CR07:001', 'ระบบต้องทำงานบน Linux Server ในรูปแบบติดตั้งเอง (LAMP) หรือ Docker Compose'],
|
||||||
['CR07:002', 'ระบบต้องใช้เขตเวลา Asia/Bangkok อย่างสม่ำเสมอทั้งแอปพลิเคชันและงานตามกำหนดเวลา', 'NFR-010'],
|
['CR07:002', 'ระบบต้องใช้เขตเวลา Asia/Bangkok อย่างสม่ำเสมอทั้งแอปพลิเคชันและงานตามกำหนดเวลา'],
|
||||||
['CR07:003', 'ระบบต้องใช้งานได้บนอุปกรณ์ Desktop และอุปกรณ์พกพาผ่าน Web Browser', 'NFR-005'],
|
['CR07:003', 'ระบบต้องใช้งานได้บนอุปกรณ์ Desktop และอุปกรณ์พกพาผ่าน Web Browser'],
|
||||||
['CR07:004', 'ระบบต้องแยกข้อมูลสาธิต/ทดสอบออกจากข้อมูลใช้งานจริงได้', 'Data requirements'],
|
['CR07:004', 'ระบบต้องแยกข้อมูลสาธิต/ทดสอบออกจากข้อมูลใช้งานจริงได้'],
|
||||||
]],
|
]],
|
||||||
['CR08', 'Operational Considerations', [
|
['CR08', 'Operational Considerations', [
|
||||||
['CR08:001', 'ระบบต้องมีการสำรองฐานข้อมูลอัตโนมัติรายวัน', 'NFR-004'],
|
['CR08:001', 'ระบบต้องมีการสำรองฐานข้อมูลอัตโนมัติรายวัน'],
|
||||||
['CR08:002', 'ระบบต้องกู้คืนข้อมูลจากชุดสำรองได้ตามขั้นตอนที่จัดทำเป็นเอกสาร', 'NFR-004'],
|
['CR08:002', 'ระบบต้องกู้คืนข้อมูลจากชุดสำรองได้ตามขั้นตอนที่จัดทำเป็นเอกสาร'],
|
||||||
['CR08:003', 'ระบบต้องมีการเฝ้าระวังสถานะบริการ Web, Database และ Node.js พร้อมแจ้งเตือนเมื่อขัดข้อง', 'NFR-004'],
|
['CR08:003', 'ระบบต้องมีการเฝ้าระวังสถานะบริการ Web, Database และ Node.js พร้อมแจ้งเตือนเมื่อขัดข้อง'],
|
||||||
['CR08:004', 'ระบบต้องรองรับหลายบริษัท (Multi-company) และหลายคลังสินค้าภายใต้การแยกข้อมูล', 'FR-024'],
|
['CR08:004', 'ระบบต้องรองรับหลายบริษัท (Multi-company) และหลายคลังสินค้าภายใต้การแยกข้อมูล'],
|
||||||
['CR08:005', 'งานตามกำหนดเวลาต้องทำงานสำเร็จโดยไม่สร้างผลลัพธ์ซ้ำหรือเกินสิทธิ์', 'FR-022'],
|
['CR08:005', 'งานตามกำหนดเวลาต้องทำงานสำเร็จโดยไม่สร้างผลลัพธ์ซ้ำหรือเกินสิทธิ์'],
|
||||||
]],
|
]],
|
||||||
['CR09', 'Maintenance Considerations', [
|
['CR09', 'Maintenance Considerations', [
|
||||||
['CR09:001', 'ซอฟต์แวร์ต้องมีโครงสร้างแบบโมดูล (Manager Classes / API) เพื่อให้แก้ไขได้โดยไม่กระทบส่วนอื่น', 'NFR-007'],
|
['CR09:001', 'ซอฟต์แวร์ต้องมีโครงสร้างแบบโมดูล (Manager Classes / API) เพื่อให้แก้ไขได้โดยไม่กระทบส่วนอื่น'],
|
||||||
['CR09:002', 'ระบบต้องมีแม่แบบค่าตั้งค่า (Configuration Template) และควบคุมเวอร์ชันด้วย Git', 'NFR-007'],
|
['CR09:002', 'ระบบต้องมีแม่แบบค่าตั้งค่า (Configuration Template) และควบคุมเวอร์ชันด้วย Git'],
|
||||||
['CR09:003', 'ระบบต้องมีเอกสารคู่มือการบำรุงรักษาและประวัติข้อบกพร่องที่แก้ไขแล้ว', 'NFR-004'],
|
['CR09:003', 'ระบบต้องมีเอกสารคู่มือการบำรุงรักษาและประวัติข้อบกพร่องที่แก้ไขแล้ว'],
|
||||||
['CR09:004', 'การเปลี่ยนแปลงโครงสร้างฐานข้อมูลต้องสะท้อนใน setup.php และตาราง schema_migrations', 'NFR-007'],
|
['CR09:004', 'การเปลี่ยนแปลงโครงสร้างฐานข้อมูลต้องสะท้อนใน setup.php และตาราง schema_migrations'],
|
||||||
]],
|
]],
|
||||||
['CR10', 'Installation Considerations', [
|
['CR10', 'Installation Considerations', [
|
||||||
['CR10:001', 'ระบบต้องติดตั้งฐานข้อมูลได้ในขั้นตอนเดียวผ่าน setup.php', 'NFR-004'],
|
['CR10:001', 'ระบบต้องติดตั้งฐานข้อมูลได้ในขั้นตอนเดียวผ่าน setup.php'],
|
||||||
['CR10:002', 'ระบบต้องติดตั้งแบบ Container ได้ด้วยคำสั่ง docker compose up -d --build', 'NFR-004'],
|
['CR10:002', 'ระบบต้องติดตั้งแบบ Container ได้ด้วยคำสั่ง docker compose up -d --build'],
|
||||||
['CR10:003', 'ระบบต้องมีสคริปต์สร้างไฟล์ .env และค่าความลับอัตโนมัติ (docker/init-env.sh)', 'NFR-001'],
|
['CR10:003', 'ระบบต้องมีสคริปต์สร้างไฟล์ .env และค่าความลับอัตโนมัติ (docker/init-env.sh)'],
|
||||||
['CR10:004', 'ระบบต้องมีเอกสารขั้นตอนการติดตั้งและตั้งค่า', 'NFR-004'],
|
['CR10:004', 'ระบบต้องมีเอกสารขั้นตอนการติดตั้งและตั้งค่า'],
|
||||||
]],
|
]],
|
||||||
['CR11', 'Support Considerations', [
|
['CR11', 'Support Considerations', [
|
||||||
['CR11:001', 'ต้องมีคู่มือผู้ใช้งาน (Software User Document)', 'Documentation'],
|
['CR11:001', 'ต้องมีคู่มือผู้ใช้งาน (Software User Document)'],
|
||||||
['CR11:002', 'ต้องมีคู่มือปฏิบัติงานสำหรับผู้ดูแลระบบ (Product Operation Guide)', 'Documentation'],
|
['CR11:002', 'ต้องมีคู่มือปฏิบัติงานสำหรับผู้ดูแลระบบ (Product Operation Guide)'],
|
||||||
['CR11:003', 'ต้องมีการอบรมผู้ใช้งานก่อนเปิดใช้งานจริง', 'Training'],
|
['CR11:003', 'ต้องมีการอบรมผู้ใช้งานก่อนเปิดใช้งานจริง'],
|
||||||
['CR11:004', 'ต้องมีช่องทางสนับสนุนและระดับการให้บริการ (SLA) หลังส่งมอบ', 'Maintenance'],
|
['CR11:004', 'ต้องมีช่องทางสนับสนุนและระดับการให้บริการ (SLA) หลังส่งมอบ'],
|
||||||
]],
|
]],
|
||||||
['CR12', 'Design Constraints', [
|
['CR12', 'Design Constraints', [
|
||||||
['CR12:001', 'ระบบต้องพัฒนาด้วย PHP, MariaDB และ Node.js ตามที่องค์กรอนุมัติ', 'NFR-008'],
|
['CR12:001', 'ระบบต้องพัฒนาด้วย PHP, MariaDB และ Node.js ตามที่องค์กรอนุมัติ'],
|
||||||
['CR12:002', 'ระบบต้องควบคุม Source Code ด้วย Git โดยมี main เป็น Baseline หลัก', 'Configuration'],
|
['CR12:002', 'ระบบต้องควบคุม Source Code ด้วย Git โดยมี main เป็น Baseline หลัก'],
|
||||||
['CR12:003', 'รหัสผ่านผู้ใช้ต้องเข้ารหัสด้วย bcrypt และไม่เก็บหรือบันทึกเป็นข้อความธรรมดา', 'NFR-002'],
|
['CR12:003', 'รหัสผ่านผู้ใช้ต้องเข้ารหัสด้วย bcrypt และไม่เก็บหรือบันทึกเป็นข้อความธรรมดา'],
|
||||||
['CR12:004', 'โครงการต้องจัดทำ Work Products ตามมาตรฐาน ISO/IEC 29110 Basic Profile', 'NFR-009'],
|
['CR12:004', 'โครงการต้องจัดทำ Work Products ตามมาตรฐาน ISO/IEC 29110 Basic Profile'],
|
||||||
]],
|
]],
|
||||||
['CR13', 'Safety and Reliability Considerations', [
|
['CR13', 'Safety and Reliability Considerations', [
|
||||||
['CR13:001', 'การเปลี่ยนแปลงฐานข้อมูลที่เกี่ยวข้องกัน (เอกสาร + สต๊อก + GL) ต้องเป็น Transaction เดียวกัน', 'NFR-003'],
|
['CR13:001', 'การเปลี่ยนแปลงฐานข้อมูลที่เกี่ยวข้องกัน (เอกสาร + สต๊อก + GL) ต้องเป็น Transaction เดียวกัน'],
|
||||||
['CR13:002', 'ระบบต้องป้องกันยอดสต๊อกติดลบและความเคลื่อนไหวซ้ำซ้อน', 'NFR-003'],
|
['CR13:002', 'ระบบต้องป้องกันยอดสต๊อกติดลบและความเคลื่อนไหวซ้ำซ้อน'],
|
||||||
['CR13:003', 'ระบบต้องรองรับการสำรองและกู้คืน (Backup & Recovery) ทั้ง Source Code และฐานข้อมูล', 'NFR-004'],
|
['CR13:003', 'ระบบต้องรองรับการสำรองและกู้คืน (Backup & Recovery) ทั้ง Source Code และฐานข้อมูล'],
|
||||||
['CR13:004', 'ระบบต้องจัดการข้อผิดพลาดโดยแสดงข้อความที่เข้าใจได้แทน Fatal Error', 'NFR-006'],
|
['CR13:004', 'ระบบต้องจัดการข้อผิดพลาดโดยแสดงข้อความที่เข้าใจได้แทน Fatal Error'],
|
||||||
]],
|
]],
|
||||||
['CR14', 'Quality Expectations', [
|
['CR14', 'Quality Expectations', [
|
||||||
['CR14:001', 'ระบบต้องผ่านการทดสอบตาม Test Case ที่กำหนดก่อนส่งมอบ', 'Acceptance'],
|
['CR14:001', 'ระบบต้องผ่านการทดสอบตาม Test Case ที่กำหนดก่อนส่งมอบ'],
|
||||||
['CR14:002', 'ทุกความต้องการต้องสอบกลับได้ถึงการออกแบบ ส่วนประกอบ และหลักฐานการทดสอบ', 'NFR-009'],
|
['CR14:002', 'ทุกความต้องการต้องสอบกลับได้ถึงการออกแบบ ส่วนประกอบ และหลักฐานการทดสอบ'],
|
||||||
['CR14:003', 'ระบบต้องผ่านการทดสอบการยอมรับ (UAT) โดยตัวแทนลูกค้าบนสภาพแวดล้อมใช้งานจริง', 'Acceptance'],
|
['CR14:003', 'ระบบต้องผ่านการทดสอบการยอมรับ (UAT) โดยตัวแทนลูกค้าบนสภาพแวดล้อมใช้งานจริง'],
|
||||||
['CR14:004', 'ต้องไม่มีข้อบกพร่องระดับวิกฤตค้างอยู่ในด้านความปลอดภัย การแยกข้อมูล และความถูกต้องของสต๊อก/บัญชี', 'Acceptance'],
|
['CR14:004', 'ต้องไม่มีข้อบกพร่องระดับวิกฤตค้างอยู่ในด้านความปลอดภัย การแยกข้อมูล และความถูกต้องของสต๊อก/บัญชี'],
|
||||||
]],
|
]],
|
||||||
];
|
];
|
||||||
export const CRS = CR_GROUPS.flatMap(([, , rows]) => rows);
|
export const CRS = CR_GROUPS.flatMap(([, , rows]) => rows);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||