docs(sdlc): reformat work products to audited reference layout

Replace the PHP/Markdown seeding with a data-driven generator in
scripts/sdlc-seed and regenerate all 59 work products.
This commit is contained in:
Thanakorn
2026-09-09 08:09:54 +07:00
parent 758922f5c4
commit 087e7b3958
209 changed files with 8063 additions and 5762 deletions
-143
View File
@@ -1,143 +0,0 @@
<?php
declare(strict_types=1);
if ($argc !== 2) {
fwrite(STDERR, "Usage: php adjust_docx_whitespace.php <document.docx>\n");
exit(2);
}
$path = $argv[1];
$wordNs = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
$zip = new ZipArchive();
if ($zip->open($path) !== true) {
throw new RuntimeException("Cannot open DOCX: {$path}");
}
$documentXml = $zip->getFromName('word/document.xml');
if ($documentXml === false) {
throw new RuntimeException('word/document.xml is missing');
}
$doc = new DOMDocument('1.0', 'UTF-8');
$doc->preserveWhiteSpace = false;
$doc->formatOutput = false;
if (!$doc->loadXML($documentXml)) {
throw new RuntimeException('word/document.xml is invalid');
}
$xpath = new DOMXPath($doc);
$xpath->registerNamespace('w', $wordNs);
function attr(DOMElement $element, string $name, string $value, string $ns): void
{
$element->setAttributeNS($ns, 'w:' . $name, $value);
}
function child(DOMDocument $doc, DOMElement $parent, string $localName, string $ns): DOMElement
{
foreach ($parent->childNodes as $node) {
if ($node instanceof DOMElement && $node->namespaceURI === $ns && $node->localName === $localName) {
return $node;
}
}
$element = $doc->createElementNS($ns, 'w:' . $localName);
$parent->appendChild($element);
return $element;
}
foreach ($xpath->query('//w:body/w:p | //w:body/w:tbl//w:p') as $paragraph) {
if (!$paragraph instanceof DOMElement) {
continue;
}
$text = '';
foreach ($xpath->query('.//w:t', $paragraph) as $textNode) {
$text .= $textNode->textContent;
}
$text = trim(preg_replace('/\s+/u', ' ', $text) ?? '');
$pPrNode = $xpath->query('./w:pPr', $paragraph)->item(0);
if (!$pPrNode instanceof DOMElement) {
$pPrNode = $doc->createElementNS($wordNs, 'w:pPr');
$paragraph->insertBefore($pPrNode, $paragraph->firstChild);
}
$spacing = child($doc, $pPrNode, 'spacing', $wordNs);
$before = '0';
$after = '30';
$line = '320';
if ($text === '') {
$after = '20';
$line = '120';
} elseif (preg_match('/^[1-7]\.\s/u', $text)) {
$before = '120';
$after = '45';
$line = '330';
} elseif (preg_match('/^[1-7]\.\d\s/u', $text)) {
$before = '70';
$after = '30';
$line = '320';
} elseif (str_starts_with($text, '•')) {
$after = '15';
$line = '310';
} elseif (str_starts_with($text, 'วันที่ 5 มกราคม')) {
$before = '40';
$after = '100';
$line = '330';
} elseif (str_starts_with($text, 'เรื่อง') || str_starts_with($text, 'เรียน')) {
$after = '55';
$line = '330';
} elseif (str_starts_with($text, 'ลงชื่อ')) {
$before = '160';
$after = '10';
$line = '310';
} elseif (str_starts_with($text, '(') || str_starts_with($text, 'ตำแหน่ง') || str_starts_with($text, 'วันที่ _') || str_starts_with($text, 'ผู้มีอำนาจ')) {
$after = '10';
$line = '310';
} elseif (str_starts_with($text, 'บริษัท บี.อาร์.เอ็น.') || str_starts_with($text, 'เอกสารฉบับนี้') || str_starts_with($text, 'ผลส่งมอบประกอบ') || str_starts_with($text, 'การเปลี่ยนแปลง') || str_starts_with($text, 'จึงจัดทำ')) {
$after = '55';
$line = '330';
}
attr($spacing, 'before', $before, $wordNs);
attr($spacing, 'after', $after, $wordNs);
attr($spacing, 'line', $line, $wordNs);
attr($spacing, 'lineRule', 'auto', $wordNs);
}
foreach ($xpath->query('//w:sectPr/w:pgMar') as $margin) {
if (!$margin instanceof DOMElement) {
continue;
}
attr($margin, 'top', '850', $wordNs);
attr($margin, 'bottom', '850', $wordNs);
attr($margin, 'left', '1080', $wordNs);
attr($margin, 'right', '1080', $wordNs);
attr($margin, 'header', '400', $wordNs);
attr($margin, 'footer', '400', $wordNs);
}
foreach ($xpath->query('//w:tbl/w:tblPr') as $tableProperties) {
if (!$tableProperties instanceof DOMElement) {
continue;
}
$margins = child($doc, $tableProperties, 'tblCellMar', $wordNs);
foreach (['top' => '45', 'left' => '70', 'bottom' => '45', 'right' => '70'] as $side => $width) {
$edge = child($doc, $margins, $side, $wordNs);
attr($edge, 'w', $width, $wordNs);
attr($edge, 'type', 'dxa', $wordNs);
}
}
$updatedXml = $doc->saveXML();
if ($updatedXml === false) {
throw new RuntimeException('Cannot serialize adjusted document XML');
}
if (!$zip->addFromString('word/document.xml', $updatedXml)) {
throw new RuntimeException('Cannot update word/document.xml');
}
$zip->close();
echo $path . PHP_EOL;
+5
View File
@@ -8,6 +8,11 @@ SOURCE_DIR="$ROOT_DIR/sdlc"
DELIVERY_ROOT="$ROOT_DIR/sdlc-delivery"
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
echo "Installing the local PDF renderer..."
npm install --prefix "$TOOL_DIR"
-117
View File
@@ -1,117 +0,0 @@
<?php
declare(strict_types=1);
/*
* Applies the approved print layout from the first formatted Progress Status
* Record to every generated Progress Status Record HTML file. Markdown source
* files are deliberately not read or changed.
*
* Usage:
* php scripts/format_progress_status_html.php
*/
$root = dirname(__DIR__);
$directory = $root . '/sdlc/1-PM Process (10 Work Product)/3.Progess Status Record';
$templatePath = $directory . '/200-WMS-26-001-00 Progress Status Record 25690123 V1.0 ApS.html';
if (!is_file($templatePath)) {
fwrite(STDERR, "Template Progress Status Record HTML was not found.\n");
exit(1);
}
$template = file_get_contents($templatePath);
if ($template === false || !preg_match('~(/\* BRN Progress Status Record print layout.*?</style>)~s', $template, $match)) {
fwrite(STDERR, "The template does not contain the approved Progress Status Record CSS.\n");
exit(1);
}
// The CSS must work for every record number, not just record 1.
$layoutCss = str_replace(
'#progress-status-record-1-of-13',
'h1[id^="progress-status-record-"]',
$match[1]
);
/** @return string */
function approvalCard(string $id, string $title, string $paragraph): string
{
$lines = preg_split('~<br\s*/?>\s*~i', trim($paragraph)) ?: [];
$items = [];
foreach ($lines as $line) {
$line = trim($line);
if ($line === '') {
continue;
}
if (preg_match('~^(Signature|Date):\s*_*\s*$~i', strip_tags($line), $label)) {
$items[] = '<p class="sign-row"><strong>' . htmlspecialchars($label[1], ENT_QUOTES, 'UTF-8') . ':</strong><span class="write-line"></span></p>';
} else {
$items[] = '<p>' . $line . '</p>';
}
}
return '<div class="approval-card"><h3 id="' . $id . '">' . $title . '</h3>' . implode("\n", $items) . '</div>';
}
$files = glob($directory . '/*.html') ?: [];
$formatted = 0;
$skipped = 0;
foreach ($files as $path) {
$html = file_get_contents($path);
if ($html === false) {
fwrite(STDERR, "Could not read: $path\n");
continue;
}
if (str_contains($html, '/* BRN Progress Status Record print layout')) {
$skipped++;
continue;
}
$html = preg_replace('~</head>~', "<style>\n$layoutCss\n</style>\n</head>", $html, 1, $headCount);
if ($headCount !== 1) {
fwrite(STDERR, "Skipped malformed HTML: " . basename($path) . "\n");
continue;
}
$header = '<header class="psr-letterhead"><img class="document-header-image" src="../../../app/assets/images/brn-document-header-left.png" alt="BRN corporate identity"><div class="psr-title-band">Progress Status Record</div></header>';
$html = preg_replace(
'~(<h1 id="progress-status-record-[^"]+">.*?</h1>\s*)<table>~s',
$header . "\n" . '$1<table class="document-control">',
$html,
1,
$titleCount
);
if ($titleCount !== 1) {
fwrite(STDERR, "Skipped unsupported record structure: " . basename($path) . "\n");
continue;
}
if (preg_match('~<h2 id="8-approval">8\. Approval</h2>(.*?)(?=</body>)~s', $html, $approvalMatch)) {
preg_match_all('~<h3 id="([^"]+)">(.*?)</h3>\s*<p>(.*?)</p>~s', $approvalMatch[1], $blocks, PREG_SET_ORDER);
if (count($blocks) === 3) {
$cards = [];
foreach ($blocks as $block) {
$cards[] = approvalCard($block[1], $block[2], $block[3]);
}
$approval = '<section class="approval-page"><h2 id="8-approval">8. Approval</h2><div class="approval-grid">'
. implode("\n", $cards)
. '</div></section>';
$html = str_replace($approvalMatch[0], $approval, $html);
}
}
$date = 'Progress Status Record';
if (preg_match('~<td>Report date</td>\s*<td>(.*?)</td>~s', $html, $dateMatch)) {
$date = trim(strip_tags($dateMatch[1]));
}
$footer = '<footer class="psr-footer">200-WMS-26-001-00 · BRN WMS · Progress Status Record · '
. htmlspecialchars($date, ENT_QUOTES, 'UTF-8') . ' · V1.0 Final</footer>';
$html = str_replace('</body>', $footer . "\n</body>", $html);
if (file_put_contents($path, $html) === false) {
fwrite(STDERR, "Could not write: $path\n");
continue;
}
$formatted++;
}
printf("Formatted: %d; already formatted: %d\n", $formatted, $skipped);
@@ -1,182 +0,0 @@
<?php
declare(strict_types=1);
$directory = __DIR__ . '/../sdlc/1-PM Process (10 Work Product)/3.Progess Status Record';
$records = [
['25690123','5–23 January 2026','Project initiation','Establish the project need, stakeholders, objectives, scope, and authority.','Formal project start and scope boundary were established retrospectively from the agreed lifecycle.','Agreed reconstructed','Statement of Work; Work Schedule','Not rated — reconstructed','Historical initiation records were not created contemporaneously.','Complete customer requirements and project planning baseline.'],
['25690218','12 January–18 February 2026','Requirements and planning baseline','Collect customer requirements and define schedule, resources, risks, controls, and work-product responsibilities.','Customer Requirements, Work Schedule, and Software Project Plan were reconstructed from agreed scope and implementation evidence.','Document reconstructed','Project Plan work products','Not rated — reconstructed','Exact elicitation dates and contemporaneous approvals are unavailable.','Begin controlled software implementation on 19 February 2026.'],
['25690225','19–25 February 2026','Application initialization','Initialize the repository and establish initial application and file-upload capability.','WMS repository initialized; verification commit and dropzone/file-upload work completed.','Git evidenced','`9a50080`, `42a3876`, `8625652`, `1843308`','Green','No unresolved blocker is evidenced in the repository for this period.','Protect configuration, address security findings, and establish the stock database foundation.'],
['25690317','9–17 March 2026','Security, database, and ICS foundation','Protect local configuration, address initial security findings, design the stock database, and introduce inventory-control modules.','Configuration was removed from tracking, security-audit fixes were applied, stock database design was committed, and ICS modules were introduced.','Git evidenced','`93d903c`, `7cb78d0`, `2e558a5`, `a4f474b`','Green','The period contains a gap in commit activity before the documented security/database work.','Develop stock visibility, warehouse structure, product controls, and operational reports.'],
['25690429','9–29 April 2026','Inventory, warehouse, and access foundation','Implement stock dashboard, products, warehouse capacity, traceability, reports, settings, authentication, and reusable managers.','Dashboard, product files, OOP utilities, capacity/occupancy, lot/serial/expiry, reports, settings, login/onboarding, and manager classes were implemented.','Git evidenced','`3b8f94f` through `db5c47b`','Green','Rapid module growth increased the need for consistent authorization and lifecycle review.','Complete document approval logic, orders, warehouse layers, barcode, and role guards.'],
['25690508','30 April–8 May 2026','Orders, barcode, and security controls','Implement approval logic, customer order flows, flexible warehouse layers, barcode operations, and centralized role guards.','Password scoring, stock approval, orders/returns/invoices, switchable warehouse layers, barcode functions, role guards, security fixes, and naming/session corrections were completed.','Git evidenced','30-Apr through 08-May commits','Green','Security gaps were actively corrected during implementation; formal correction records remain to be linked.','Prepare production setup and introduce accounting capabilities.'],
['25690513','11–13 May 2026','Production preparation and accounting foundation','Prepare deployment, automate setup, correct onboarding/recovery, populate test data, and introduce accounting.','Production preparation, dynamic base URL, automated setup, password recovery, onboarding fixes, test data, dashboard updates, and accounting modules were committed.','Git evidenced','11–13 May commits','Green','Multiple fixes and a revert occurred during integration; results require traceability to tests.','Integrate accounting workflows, document control, access limits, and supporting services.'],
['25690523','20–23 May 2026','Accounting integration and supporting services','Integrate accounting, user invitation/access controls, transaction limits, document flows, Node.js, aggregates, and reports.','Accounting workflows, access controls, setup script, soft delete, Socket service, GL aggregation, accounting reports, journal batching, and GL automation were implemented.','Git evidenced','20–23 May commits','Green','Integration breadth increased regression and tenant-isolation risk.','Complete security hardening, notification control, sequencing, scheduler, tenant scoping, and final review.'],
['25690529','24–29 May 2026','Development baseline','Harden security and integrity, close known implementation gaps, stabilize services, and establish the substantially complete development baseline.','Session controls, aggregates, notifications, security/lifecycle reviews, transaction limits, document sequencing, role guards, scheduler fixes, bin naming, tenant scoping, and final refactoring were completed.','Git evidenced','24–29 May commits; final `a0677d6`','Green','Formal verification, validation, and acceptance evidence remained to be consolidated after development.','Conduct verification, validation, documentation, and delivery preparation.'],
['25690731','30 May–31 July 2026','Verification, validation, and documentation','Verify requirements/design/software, validate intended use, prepare operational documents, and consolidate delivery evidence.','This activity period is part of the agreed timeline, but contemporaneous Git evidence is unavailable; assurance and documentation records are being reconstructed from the delivered system.','Document reconstructed','SRS, Design, Traceability, Test, Guide, Verification, and Validation work products','Amber','Missing contemporaneous evidence creates an audit and acceptance gap.','Complete stabilization corrections, finalize evidence, and obtain stakeholder review.'],
['25690803','3 August 2026','Stabilization correction','Correct login and environment-configuration issues identified after the development baseline.','Login and configuration corrections were committed.','Git evidenced','`b2c4374`','Green','The correction requires linkage to the Correction Register and verification evidence.','Prepare representative demonstration data and complete final delivery checks.'],
['25690814','4–14 August 2026','Demonstration and completion boundary','Prepare controlled demonstration data, complete final checks, and reach the agreed project-completion boundary.','Demonstration data was committed on 14 August 2026 and the agreed formal completion date was reached.','Git evidenced / agreed boundary','`dd48a8b`; agreed completion date','Green','Formal signatures and some controlled ISO/IEC 29110 evidence remained open at completion.','Consolidate remaining work products and obtain Project Sponsor authorization.'],
['25690817','15–17 August 2026','Evidence consolidation and authorization preparation','Complete missing controlled work products, align roles, and prepare records for review and authorization.','PM work products were converted to reviewable Markdown, project identities and roles were aligned, and missing evidence was identified for subsequent completion.','Document reconstructed','Current `sdlc/` repository and Git status','Amber','Verification, validation, repository-backup, and acceptance evidence and signatures remain open.','Complete remaining PM/SI work products and obtain Project Sponsor review and authorization.'],
];
$tasksByDate = [
'25690123' => [
['1.1','Identify project need','09-Jan-2026','09-Jan-2026','100%','Statement of Work','Reconstructed completed'],
['1.2','Identify stakeholders and objectives','16-Jan-2026','16-Jan-2026','100%','Project role confirmation','Reconstructed completed'],
['1.3','Approve project scope','23-Jan-2026','Pending signature','90%','Statement of Work V1.0 Final','Approval pending'],
],
'25690218' => [
['2.1','Collect customer requirements','06-Feb-2026','06-Feb-2026','100%','Customer Requirements V1.0 Final','Reconstructed completed'],
['2.2','Prepare Software Project Plan','13-Feb-2026','13-Feb-2026','100%','Software Project Plan V1.0 Final','Reconstructed completed'],
['2.3','Baseline requirements and schedule','18-Feb-2026','18-Feb-2026','100%','Work Schedule and Project Plan','Reconstructed completed; approval pending'],
],
'25690225' => [
['3.1','Initialize WMS application','25-Feb-2026','25-Feb-2026','100%','Git `9a50080`–`1843308`','Completed'],
],
'25690317' => [
['3.2','Security and database foundation','17-Mar-2026','17-Mar-2026','100%','Git `93d903c`–`a4f474b`','Completed'],
],
'25690429' => [
['3.3','Inventory and warehouse modules','29-Apr-2026','29-Apr-2026','100%','Git `3b8f94f`–`db5c47b`','Completed'],
],
'25690508' => [
['3.4','Authentication and onboarding','12-May-2026','In progress at 08-May','70%','Commits through 08-May-2026','In progress; carried forward'],
['3.5','Order and barcode workflows','08-May-2026','08-May-2026','100%','02–08 May commits','Completed'],
],
'25690513' => [
['3.4','Authentication and onboarding','12-May-2026','12-May-2026','100%','Login/onboarding and recovery commits','Completed'],
['3.6','Production preparation and setup','13-May-2026','13-May-2026','100%','11–13 May commits','Completed'],
['3.7','Accounting and finance workflows','23-May-2026','In progress at 13-May','20%','Accounting foundation commits','In progress; carried forward'],
],
'25690523' => [
['3.7','Accounting and finance workflows','23-May-2026','23-May-2026','100%','13–23 May accounting commits','Completed'],
['3.8','Real-time services and scheduled jobs','27-May-2026','In progress at 23-May','45%','Node.js and GL aggregate commits','In progress; carried forward'],
['3.9','Security hardening and lifecycle review','28-May-2026','In progress at 23-May','35%','Access, flow, and role-guard commits','In progress; carried forward'],
],
'25690529' => [
['3.8','Real-time services and scheduled jobs','27-May-2026','27-May-2026','100%','22–27 May Node.js/scheduler commits','Completed'],
['3.9','Security hardening and lifecycle review','28-May-2026','28-May-2026','100%','21–28 May hardening/review commits','Completed'],
['3.10','Refactor and development baseline','29-May-2026','29-May-2026','100%','Git `a0677d6`','Completed'],
],
'25690731' => [
['4.3','Verify requirements, design, and implementation','31-Jul-2026','Evidence consolidation pending','80%','Reconstructed verification work products','In progress; evidence gap'],
['4.4','Customer-oriented system validation','07-Aug-2026','In progress at 31-Jul','85%','Reconstructed validation activities','In progress; carried forward'],
['4.5','Prepare operational documentation','07-Aug-2026','In progress at 31-Jul','85%','Configuration and draft work products','In progress; carried forward'],
],
'25690803' => [
['4.6','Configuration and login corrections','03-Aug-2026','03-Aug-2026','100%','Git `b2c4374`','Completed; formal correction closure pending'],
],
'25690814' => [
['4.4','Customer-oriented system validation','07-Aug-2026','Evidence consolidation pending','90%','Validation work products','In progress; approval pending'],
['4.5','Prepare operational documentation','07-Aug-2026','Evidence consolidation pending','90%','Operational documentation work products','In progress; approval pending'],
['4.7','Prepare demonstration data','14-Aug-2026','14-Aug-2026','100%','Git `dd48a8b`','Completed'],
['5.1','Final repository and work-product review','14-Aug-2026','In progress','70%','Repository and SDLC gap review','In progress; carried forward'],
['5.2','Acceptance and project closure','14-Aug-2026','Pending signature','75%','Completion boundary reached','Acceptance pending'],
],
'25690817' => [
['5.1','Final repository and work-product review','14-Aug-2026','In progress at 17-Aug','80%','BRN WMS PM work products and gap list','In progress'],
['5.2','Acceptance and project closure','14-Aug-2026','Pending evidence and signature','75%','Acceptance evidence not yet authorized','Acceptance pending'],
],
];
foreach ($records as $index => $record) {
[$date,$period,$milestone,$planned,$completed,$evidenceClass,$evidence,$rag,$issue,$next] = $record;
$number = $index + 1;
$taskRows = '';
foreach ($tasksByDate[$date] as $task) {
$taskRows .= '| ' . implode(' | ', $task) . " |\n";
}
$content = <<<MD
# Progress Status Record {$number} of 13
| Document field | Value |
|---|---|
| Document | Progress Status Record |
| Project | BRN WMS |
| Project code | 200-WMS-26-001-00 |
| Reporting period | {$period} |
| Report date | {$date} |
| Actual preparation date | 17 August 2026 |
| Release | {$date} V1.0 Final |
| Prepared by | คุณอภิรัชต์ สุภัทรประทีป — Project Manager |
| Technical evidence | ธนกร สถิตวิทยากุล — Developer / System Analyst |
| Status | Final — ready for review and authorization |
## 1. Retrospective-record disclosure
This record was prepared retrospectively on 17 August 2026. It covers the historical reporting period shown above and does not claim to have been authored or committed on the historical report date. Statements are limited to the evidence classification and references identified below.
## 2. Period objective
**Milestone:** {$milestone}
**Planned outcome:** {$planned}
## 3. Task progress
**Period summary:** {$completed}
| Task ID | Task | Planned finish | Actual / evidence date | Completion | Evidence | Status |
|---|---|---:|---:|---:|---|---|
{$taskRows}
| Evidence field | Value |
|---|---|
| Evidence classification | {$evidenceClass} |
| Evidence reference | {$evidence} |
| Overall period status | {$rag} |
## 4. Schedule status
Work is assessed against the agreed project boundaries of 5 January–14 August 2026. No unsupported numeric variance is asserted for this period. Where the evidence is reconstructed, schedule status remains explicitly unrated rather than represented as contemporaneously measured.
## 5. Issues, risks, and corrective action
**Issue or risk:** {$issue}
**Control:** Preserve the stated evidence basis, link applicable defects to the Correction Register, update traceability and tests, and obtain the required review or authorization without backdating records or signatures.
## 6. Changes
Technical commits in this period are implementation evidence. They must be classified separately as in-scope implementation, defect correction, or an authorized baseline change before being entered in the Change Report.
## 7. Next-period plan
{$next}
## 8. Approval
### Prepared by
Name: คุณอภิรัชต์ สุภัทรประทีป
Role: Project Manager
Signature: ______________________________________________
Date: ___________________________________________________
### Technical evidence provided by
Name: ธนกร สถิตวิทยากุล
Role: Developer / System Analyst
Signature: ______________________________________________
Date: ___________________________________________________
### Reviewed and authorized by
Name: คุณเสรี วิริยะสกุลธรณ์
Project roles: Project Sponsor / Customer Representative / Authorized Approver
Position: กรรมการผู้จัดการ
Company: บริษัท บี.อาร์.เอ็น. เอ็นเตอร์ไพรส์ จำกัด
Signature: ______________________________________________
Date: ___________________________________________________
MD;
$filename = "200-WMS-26-001-00 Progress Status Record {$date} V1.0 ApS.md";
if (file_put_contents($directory . '/' . $filename, $content . PHP_EOL) === false) {
throw new RuntimeException('Unable to write ' . $filename);
}
}
require __DIR__ . '/normalize_document_dates.php';
echo count($records) . " progress status records generated.\n";
-80
View File
@@ -1,80 +0,0 @@
<?php
declare(strict_types=1);
$base = __DIR__ . '/../sdlc/1-PM Process (10 Work Product)/2.Project Plan/';
$w = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
function e(string $s): string { return htmlspecialchars($s, ENT_XML1 | ENT_QUOTES, 'UTF-8'); }
function r(string $s, bool $b = false, int $sz = 28): string {
return '<w:r><w:rPr>' . ($b ? '<w:b/><w:bCs/>' : '') . '<w:rFonts w:ascii="TH Sarabun New" w:hAnsi="TH Sarabun New" w:eastAsia="TH Sarabun New" w:cs="TH Sarabun New"/><w:sz w:val="'.$sz.'"/><w:szCs w:val="'.$sz.'"/></w:rPr><w:t xml:space="preserve">'.e($s).'</w:t></w:r>';
}
function p(string $s = '', string $a = 'left', bool $b = false, int $sz = 28, int $before = 0, int $after = 40): string {
return '<w:p><w:pPr><w:jc w:val="'.$a.'"/><w:spacing w:before="'.$before.'" w:after="'.$after.'" w:line="320" w:lineRule="auto"/></w:pPr>'.r($s,$b,$sz).'</w:p>';
}
function h(string $no, string $title): string { return p($no.' '.$title,'left',true,31,120,45); }
function b(string $s): string { return '<w:p><w:pPr><w:ind w:left="650" w:hanging="300"/><w:spacing w:after="20" w:line="310" w:lineRule="auto"/></w:pPr>'.r('• '.$s,false,28).'</w:p>'; }
function pb(): string { return '<w:p><w:r><w:br w:type="page"/></w:r></w:p>'; }
function tr(array $cells, bool $head = false, array $widths = []): string {
$x='<w:tr>';
foreach($cells as $i=>$v){$width=$widths[$i]??2200;$x.='<w:tc><w:tcPr><w:tcW w:w="'.$width.'" w:type="dxa"/>'.($head?'<w:shd w:fill="D9EAF7"/>':'').'</w:tcPr>'.p((string)$v,'left',$head,25,0,15).'</w:tc>';}
return $x.'</w:tr>';
}
function tbl(array $rows, array $widths, bool $firstHead = true): string {
$x='<w:tbl><w:tblPr><w:tblW w:w="9000" w:type="dxa"/><w:tblBorders><w:top w:val="single" w:sz="4" w:color="8AA4B5"/><w:left w:val="single" w:sz="4" w:color="8AA4B5"/><w:bottom w:val="single" w:sz="4" w:color="8AA4B5"/><w:right w:val="single" w:sz="4" w:color="8AA4B5"/><w:insideH w:val="single" w:sz="4" w:color="B7C9D6"/><w:insideV w:val="single" w:sz="4" w:color="B7C9D6"/></w:tblBorders></w:tblPr><w:tblGrid>';
foreach($widths as $wd)$x.='<w:gridCol w:w="'.$wd.'"/>';$x.='</w:tblGrid>';
foreach($rows as $i=>$row)$x.=tr($row,$firstHead&&$i===0,$widths);
return $x.'</w:tbl>';
}
function top(string $label): string {
return '<w:tbl><w:tblPr><w:tblW w:w="9000" w:type="dxa"/></w:tblPr><w:tblGrid><w:gridCol w:w="6000"/><w:gridCol w:w="3000"/></w:tblGrid><w:tr><w:tc><w:tcPr><w:tcW w:w="6000" w:type="dxa"/></w:tcPr>'.p('B.R.N. ENTERPRISE CO., LTD.','left',true,29,0,0).p('1011 Supalai Grand Tower, 7th Floor, Unit 6-7, Rama 3 Road, Bangkok 10120','left',false,18,0,0).'</w:tc><w:tc><w:tcPr><w:tcW w:w="3000" w:type="dxa"/><w:shd w:fill="0798D1"/><w:vAlign w:val="center"/></w:tcPr>'.p($label,'center',false,29,0,0).'</w:tc></w:tr></w:tbl>';
}
function meta(string $doc, string $title): string {
return tbl([
['Document No',$doc,'Release, Version','05/01/26 V1.0 Final'],
['Project Name','โครงการพัฒนาระบบบริหารจัดการคลังสินค้า BRN WMS','Project Code','200-WMS-26-001-00'],
['Title',$title,'Project Period','5 มกราคม – 14 สิงหาคม 2569'],
['Prepared by','คุณอภิรัชต์ สุภัทรประทีป (Project Manager)','Approval','Pending authorized signature'],
],[1600,3500,1600,2300],false);
}
function saveDoc(string $path,string $title,string $body,string $code): void {
$document='<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><w:body>'.$body.'<w:sectPr><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="850" w:right="1080" w:bottom="850" w:left="1080" w:header="400" w:footer="400"/><w:footerReference w:type="default" r:id="rId1"/></w:sectPr></w:body></w:document>';
$ct='<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/><Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/></Types>';
$rels='<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/></Relationships>';
$drels='<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/></Relationships>';
$footer='<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'.p('ISO/IEC 29110 | 200-WMS-26-001-00 | '.$code.' | V1.0 Final','center',false,18,0,0).'</w:ftr>';
$core='<?xml version="1.0" encoding="UTF-8" standalone="yes"?><cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dc:title>'.e($title).'</dc:title><dc:creator>คุณอภิรัชต์ สุภัทรประทีป (Project Manager)</dc:creator><dc:description>Editable ISO/IEC 29110 controlled final document ready for authorization.</dc:description><dcterms:created xsi:type="dcterms:W3CDTF">2026-01-05T00:00:00+07:00</dcterms:created></cp:coreProperties>';
$z=new ZipArchive();if($z->open($path,ZipArchive::CREATE|ZipArchive::OVERWRITE)!==true)throw new RuntimeException('Cannot create '.$path);
foreach(['[Content_Types].xml'=>$ct,'_rels/.rels'=>$rels,'word/document.xml'=>$document,'word/_rels/document.xml.rels'=>$drels,'word/footer1.xml'=>$footer,'docProps/core.xml'=>$core] as $n=>$v)$z->addFromString($n,$v);$z->close();
}
$plan = top('Software Project Plan').p().meta('Software Project Plan','แผนการดำเนินโครงการ').h('1.','วัตถุประสงค์และภาพรวม')
.p('โครงการ BRN WMS จัดทำระบบเว็บแบบหลายบริษัทและหลายคลัง เพื่อควบคุมข้อมูลหลัก สินค้าคงคลัง เอกสารซื้อขาย การเงินและบัญชี รายงาน และการแจ้งเตือน โดยบริหารงานตามกระบวนการ Project Management และ Software Implementation ของ ISO/IEC 29110 Basic Profile.')
.h('2.','ขอบเขตและผลส่งมอบ').b('ซอฟต์แวร์ BRN WMS และฐานข้อมูลสำหรับ Authentication/Company และ WMS/Accounting').b('โมดูลผู้ใช้ บริษัท ผู้ติดต่อ สินค้า คลัง/ตำแหน่งจัดเก็บ Stock In/Out/Transfer Lot Serial Expiry และ Barcode').b('โมดูล Quotation, Order, Return, Purchase, Invoice, Receipt, Payment, General Ledger และรายงาน').b('Node.js Socket.IO สำหรับแจ้งเตือน และ Scheduler สำหรับ Stock/GL maintenance และ alerts').b('Work Products ISO/IEC 29110 จำนวน 22 รายการและ Project Repository ที่ควบคุมเวอร์ชัน')
.h('3.','แนวทางดำเนินโครงการ').tbl([['Phase','Period','Primary output'],['Initiation and planning','05-Jan–18-Feb-2026','SOW, Customer Requirements, Project Plan'],['Implementation','19-Feb–29-May-2026','Requirements, Design, Components, Tests, Software'],['Verification, validation and documentation','30-May–07-Aug-2026','Test/Verification/Validation results and guides'],['Stabilization and closure','03-Aug–14-Aug-2026','Corrections, demo data, acceptance and repository closure']],[2600,2500,3900])
.h('4.','ทีมงานและความรับผิดชอบ').tbl([['Role','Responsibility'],['Project Sponsor / Customer Representative / Authorized Approver — คุณเสรี วิริยะสกุลธรณ์','Represent customer needs and authorize scope, resources, changes and acceptance.'],['Project Manager — คุณอภิรัชต์ สุภัทรประทีป','Plan, monitor, communicate, control risks/issues and close the project.'],['Developer / System Analyst — ธนกร สถิตวิทยากุล','Analyze, design, implement, configure, correct and maintain technical records aligned with Git evidence.'],['Tester / Reviewer','Prepare and execute tests; verify work products and record results.'],['Document Control','Control identifiers, versions, approvals, repository and evidence.']],[3200,5800])
.h('5.','ทรัพยากรและสภาพแวดล้อม').b('PHP 8+, MySQL/MariaDB, Nginx or compatible web server, Node.js, npm and PM2').b('Git main branch as source baseline; configuration secrets excluded from version control').b('Two logical databases: identity/company data and WMS/accounting operational data').b('Development and deployment in Asia/Bangkok time zone with controlled configuration')
.h('6.','การบริหารความเสี่ยง').tbl([['Risk','Response / Control'],['Reconstructed pre-development evidence','Mark inferred records clearly and obtain retrospective review/approval.'],['Unauthorized access or tenant-data exposure','Role guards, company/warehouse scoping, session controls and security review.'],['Inventory inconsistency','Transactions, validation, locking, approval flow, stock summaries and reconciliation tests.'],['Configuration or secret exposure','Ignored local configuration, templates, deployment guide and restricted web access.'],['Insufficient acceptance evidence','Trace requirements to tests and obtain authorized signatures before baselining.'],['Scope expansion','Record Change Report, assess impact and authorize before implementation.']],[3600,5400])
.h('7.','คุณภาพ การทวนสอบ และการตรวจสอบความใช้ได้').b('Every approved requirement receives a unique ID and forward/backward traceability.').b('Reviews cover requirements, design, code/configuration, test records and operational documentation.').b('Critical failures prevent acceptance; other defects require disposition in the Correction Register.').b('Validation uses representative warehouse, order, finance and role-based workflows with controlled demo data.')
.h('8.','การควบคุมการเปลี่ยนแปลงและการกำหนดค่า').b('Configuration items include source code, database/setup scripts, Node.js services, controlled documents and releases.').b('Changes are requested, analyzed, approved, implemented, tested and closed with linked evidence.').b('Document filenames use project code, work-product name, Buddhist date, version and status/author indicator.')
.h('9.','การสื่อสารและการติดตาม').tbl([['Record','Frequency / trigger','Owner'],['Progress Status Record','Weekly during active work','Project Manager'],['Meeting Record','At project/review/decision meetings','Recorder'],['Correction Register','When an issue or defect is identified','Project Manager / Tester'],['Change Report','When baseline scope/configuration changes','Project Manager'],['Repository review','At baselines and closure','Document Control']],[3400,3300,2300])
.h('10.','เกณฑ์การส่งมอบและปิดโครงการ').b('Scope functions pass their linked test cases and no unresolved critical defect remains.').b('Installation, user, operation and maintenance information is available and reviewed.').b('Software and all required work products are stored in the controlled repository.').b('Verification, validation and acceptance results are reviewed and authorized.').p('Approval signature: ____________________________________','center',false,28,180,20).p('Name/position: _______________________________________','center').p('Date: ________________________________________________','center');
$reqs = [
['FR-001','Identity','Register/onboard company owner and invited users.','Must'],['FR-002','Identity','Authenticate users and apply role-based access for Owner, Admin, Staff and Viewer.','Must'],['FR-003','Identity','Support password recovery, session control and applicable OTP verification.','Must'],['FR-004','Administration','Maintain company profile, SMTP, system settings, users and application access.','Must'],
['FR-005','Master data','Maintain warehouses, storage areas/bins, product categories, products and contacts.','Must'],['FR-006','Master data','Support simple and layered warehouse-location models.','Should'],['FR-007','Inventory','Record stock-in with product, quantity, warehouse/bin and traceability attributes.','Must'],['FR-008','Inventory','Record stock-out with authorization and balance validation.','Must'],['FR-009','Inventory','Transfer stock between controlled warehouse locations.','Must'],['FR-010','Inventory','Track lot, serial number and expiry-date information where applicable.','Must'],['FR-011','Inventory','Display stock overview, movement history, capacity/occupancy and low-stock information.','Must'],['FR-012','Inventory','Generate SKU and location barcode labels and support scanning workflows.','Should'],
['FR-013','Sales','Create and manage quotations, sales orders, invoices, returns and credit notes.','Must'],['FR-014','Purchasing','Create and manage purchase requests, purchase orders, supplier invoices and supplier returns.','Must'],['FR-015','Finance','Create and manage receipt/payment billing, receipts and payments.','Must'],['FR-016','Accounting','Maintain chart of accounts, departments, formulas, journals and GL entries.','Must'],['FR-017','Accounting','Produce trial balance, profit/loss, balance sheet, VAT and GL movement reports.','Must'],['FR-018','Documents','Generate controlled document numbers and manage document lifecycle/status.','Must'],['FR-019','Documents','Attach permitted files to supported records.','Should'],['FR-020','Reports','Filter, view and export operational and management reports.','Must'],
['FR-021','Notifications','Notify authorized users of relevant status transitions and operational alerts.','Should'],['FR-022','Scheduler','Maintain stock/GL summaries and produce low-stock/overdue-invoice alerts.','Should'],['FR-023','Audit','Preserve creator/updater/status and transaction history required for review.','Must'],['FR-024','Isolation','Restrict company and warehouse data to the authorized user context.','Must'],
['NFR-001','Security','Protect secrets from source control and direct web access.','Must'],['NFR-002','Security','Validate input, authorize server-side actions and prevent cross-tenant access.','Must'],['NFR-003','Integrity','Execute related database updates transactionally and prevent invalid negative/duplicate movements.','Must'],['NFR-004','Availability','Provide documented installation, configuration, backup and recovery procedures.','Must'],['NFR-005','Usability','Provide responsive browser UI suitable for desktop and warehouse-floor devices.','Should'],['NFR-006','Performance','Provide practical response times for daily operations and use aggregate tables for dashboards/reports.','Should'],['NFR-007','Maintainability','Use modular managers/APIs, centralized helpers, configuration templates and version control.','Should'],['NFR-008','Compatibility','Run on PHP 8+, MySQL/MariaDB and current standards-based browsers.','Must'],['NFR-009','Traceability','Link each approved requirement to design, component and verification evidence.','Must'],['NFR-010','Time','Use Asia/Bangkok consistently for application and scheduled jobs.','Must'],
];
$cr = top('Customer Requirements').p().meta('Customer Requirements','เอกสารบันทึกและสรุปความต้องการของลูกค้า').h('1.','วัตถุประสงค์').p('เอกสารฉบับนี้กำหนดความต้องการระดับลูกค้าและผู้ใช้ของ BRN WMS เพื่อเป็นฐานสำหรับ Software Requirements Specification, Software Design, Traceability Record, Test Cases และ Acceptance. ข้อมูลถูกรวบรวมย้อนหลังจากขอบเขตที่ตกลง ซอร์สโค้ด ฐานข้อมูล และประวัติ Git โดยธนกร สถิตวิทยากุล ในบทบาท Developer / System Analyst และต้องได้รับการทบทวนและอนุมัติโดยคุณเสรี วิริยะสกุลธรณ์ ในบทบาท Project Sponsor / Customer Representative / Authorized Approver ก่อน Baseline.')
.h('2.','ผู้มีส่วนได้ส่วนเสีย').tbl([['Stakeholder','Interest / role'],['Authorized Sponsor','Approve scope, resources, changes and final acceptance.'],['Warehouse Manager / Staff','Accurate receiving, issuing, transfer, stock status, traceability and reports.'],['Sales and Purchasing Users','Controlled quotation/order/purchase/invoice/return workflows.'],['Finance and Accounting Users','Billing, receipts, payments, journals, GL and financial reports.'],['System Administrator','Company configuration, user access, deployment, monitoring and recovery.'],['Auditor / Management','Traceable transactions, controlled records and management information.']],[3400,5600])
.h('3.','สมมติฐานและข้อจำกัด').b('The solution is a browser-based application deployed on a controlled PHP/MySQL environment.').b('External ERP integration, legacy-data migration, hardware procurement and custom third-party services are outside scope unless changed formally.').b('Customer representatives provide representative data, review requirements and participate in validation and acceptance.').b('Requirements marked Must are acceptance-critical; Should requirements may be accepted with documented disposition.')
.h('4.','รายการความต้องการ');
$reqRows=[['ID','Area','Requirement','Priority']];foreach($reqs as $q)$reqRows[]=$q;$cr.=tbl($reqRows,[1300,1700,5100,900]).h('5.','เกณฑ์การยอมรับความต้องการ').b('Each Must requirement is implemented, traced to one or more test cases and passes verification/validation.').b('No critical security, tenant-isolation, inventory-integrity or transaction defect remains open.').b('Any exception or deferred Should requirement is recorded and authorized.').b('Customer review confirms that representative end-to-end workflows support intended warehouse operations.').h('6.','การอนุมัติ Baseline').p('Customer representative signature: ______________________________','center',false,28,180,20).p('Name/position: ______________________________________________','center').p('Date: ________________________________________________________','center');
saveDoc($base.'2-Software Project Plan/200-WMS-26-001-00 Software Project Plan 25690105 V1.0 Final.docx','200-WMS-26-001-00 Software Project Plan',$plan,'PP');
saveDoc($base.'3-Customer Requirement/200-WMS-26-001-00 Customer Requirements 25690112 V1.0 Final.docx','200-WMS-26-001-00 Customer Requirements',$cr,'CR');
echo "Project Plan documents generated.\n";
require __DIR__ . '/normalize_document_dates.php';
-192
View File
@@ -1,192 +0,0 @@
<?php
declare(strict_types=1);
/**
* Generate the ISO/IEC 29110 Statement of Work for BRN WMS.
*
* The document is generated as native OOXML so it remains editable in Word.
*/
$output = __DIR__ . '/../sdlc/1-PM Process (10 Work Product)/1.Statement of Work/'
. '200-WMS-26-001-00 Statement of Work 25690105 V1.0 Final.docx';
function xml(string $value): string
{
return htmlspecialchars($value, ENT_XML1 | ENT_QUOTES, 'UTF-8');
}
function run(string $text, bool $bold = false, int $size = 30): string
{
$boldXml = $bold ? '<w:b/><w:bCs/>' : '';
return '<w:r><w:rPr>' . $boldXml
. '<w:rFonts w:ascii="TH Sarabun New" w:hAnsi="TH Sarabun New" w:eastAsia="TH Sarabun New" w:cs="TH Sarabun New"/>'
. '<w:sz w:val="' . $size . '"/><w:szCs w:val="' . $size . '"/>'
. '<w:lang w:val="th-TH" w:eastAsia="th-TH" w:bidi="th-TH"/>'
. '</w:rPr><w:t xml:space="preserve">' . xml($text) . '</w:t></w:r>';
}
function para(string $text = '', string $align = 'left', bool $bold = false, int $size = 30, int $before = 0, int $after = 80, int $indent = 0): string
{
return '<w:p><w:pPr><w:jc w:val="' . $align . '"/>'
. '<w:spacing w:before="' . $before . '" w:after="' . $after . '" w:line="360" w:lineRule="auto"/>'
. ($indent > 0 ? '<w:ind w:firstLine="' . $indent . '"/>' : '')
. '</w:pPr>' . run($text, $bold, $size) . '</w:p>';
}
function bullet(string $text): string
{
return '<w:p><w:pPr><w:ind w:left="720" w:hanging="360"/><w:spacing w:after="50" w:line="340" w:lineRule="auto"/></w:pPr>'
. run('• ' . $text, false, 29) . '</w:p>';
}
function tableRow(string $label, string $value): string
{
return '<w:tr>'
. '<w:tc><w:tcPr><w:tcW w:w="2200" w:type="dxa"/><w:shd w:fill="D9EAF7"/></w:tcPr>'
. para($label, 'left', true, 27, 0, 20) . '</w:tc>'
. '<w:tc><w:tcPr><w:tcW w:w="6800" w:type="dxa"/></w:tcPr>'
. para($value, 'left', false, 27, 0, 20) . '</w:tc>'
. '</w:tr>';
}
$header = '<w:tbl><w:tblPr><w:tblW w:w="9000" w:type="dxa"/><w:tblLayout w:type="fixed"/></w:tblPr><w:tblGrid><w:gridCol w:w="6100"/><w:gridCol w:w="2900"/></w:tblGrid>'
. '<w:tr><w:tc><w:tcPr><w:tcW w:w="6100" w:type="dxa"/></w:tcPr>'
. para('B.R.N. ENTERPRISE CO., LTD.', 'left', true, 29, 0, 0)
. para('1011 Supalai Grand Tower, 7th Floor, Unit 6-7, Rama 3 Road,', 'left', false, 19, 0, 0)
. para('Chongnonsi, Yannawa, Bangkok 10120', 'left', false, 19, 0, 0)
. '</w:tc><w:tc><w:tcPr><w:tcW w:w="2900" w:type="dxa"/><w:shd w:fill="0798D1"/><w:vAlign w:val="center"/></w:tcPr>'
. para('Statement of Work', 'center', false, 29, 0, 0)
. '</w:tc></w:tr></w:tbl>';
$meta = '<w:tbl><w:tblPr><w:tblW w:w="9000" w:type="dxa"/><w:tblBorders>'
. '<w:top w:val="single" w:sz="4" w:color="9E9E9E"/><w:left w:val="single" w:sz="4" w:color="9E9E9E"/>'
. '<w:bottom w:val="single" w:sz="4" w:color="9E9E9E"/><w:right w:val="single" w:sz="4" w:color="9E9E9E"/>'
. '<w:insideH w:val="single" w:sz="4" w:color="9E9E9E"/><w:insideV w:val="single" w:sz="4" w:color="9E9E9E"/>'
. '</w:tblBorders></w:tblPr><w:tblGrid><w:gridCol w:w="2200"/><w:gridCol w:w="6800"/></w:tblGrid>'
. tableRow('รหัสโครงการ', '200-WMS-26-001-00')
. tableRow('ชื่อโครงการ', 'โครงการพัฒนาระบบบริหารจัดการคลังสินค้า BRN WMS')
. tableRow('ระยะเวลาโครงการ', '5 มกราคม 2569 – 14 สิงหาคม 2569')
. tableRow('เอกสาร', 'Statement of Work · Version 1.0 · สถานะ Final')
. '</w:tbl>';
$body = $header
. para('', 'left', false, 10, 0, 80)
. para('วันที่ 5 มกราคม 2569', 'center', false, 30, 80, 180)
. para('เรื่อง ขอบเขตการดำเนินงานโครงการพัฒนาระบบบริหารจัดการคลังสินค้า BRN WMS', 'left', true, 30, 0, 100)
. para('เรียน ผู้บริหารและผู้มีส่วนเกี่ยวข้องในโครงการ', 'left', true, 30, 0, 180)
. para('บริษัท บี.อาร์.เอ็น. เอ็นเตอร์ไพรส์ จำกัด มีความประสงค์ดำเนินโครงการพัฒนาระบบบริหารจัดการคลังสินค้า BRN WMS เพื่อเพิ่มความถูกต้องและความรวดเร็วของงานคลังสินค้า ทำให้สามารถติดตามสินค้าคงคลัง การเคลื่อนไหวสินค้า คำสั่งซื้อ เอกสารทางธุรกิจ และข้อมูลบัญชีที่เกี่ยวข้องได้อย่างเป็นระบบ โดยใช้ข้อมูลแบบรวมศูนย์และกำหนดสิทธิ์การเข้าถึงตามบทบาทผู้ใช้งาน', 'both', false, 30, 0, 120, 720)
. para('เอกสารฉบับนี้กำหนดขอบเขต ผลส่งมอบ เกณฑ์การยอมรับ หน้าที่ความรับผิดชอบ และกรอบระยะเวลาของโครงการตามแนวทาง ISO/IEC 29110 โดยมีรายละเอียดดังต่อไปนี้', 'both', false, 30, 0, 180, 720)
. $meta
. para('1. วัตถุประสงค์', 'left', true, 32, 180, 80)
. bullet('พัฒนาระบบเว็บสำหรับควบคุมสินค้าคงคลังและการปฏิบัติงานคลังสินค้าแบบหลายคลัง')
. bullet('สนับสนุนการรับเข้า เบิกจ่าย โอนย้าย ปรับปรุง และตรวจสอบยอดคงเหลือ พร้อมการติดตาม Lot, Serial Number และวันหมดอายุ')
. bullet('ลดความผิดพลาดจากการทำงานด้วยมือและเพิ่มความสามารถในการตรวจสอบย้อนหลัง')
. bullet('สนับสนุนการบริหารคำสั่งซื้อ จัดซื้อ คืนสินค้า ใบแจ้งหนี้ รายงาน และรายการบัญชีที่เกี่ยวข้อง')
. bullet('จัดให้มีการรักษาความมั่นคงปลอดภัย การแยกข้อมูลรายบริษัท การกำหนดสิทธิ์ และการแจ้งเตือนแบบเรียลไทม์')
. para('2. ขอบเขตงาน', 'left', true, 32, 150, 80)
. para('2.1 งานวิเคราะห์และออกแบบ', 'left', true, 29, 0, 50)
. bullet('รวบรวมและวิเคราะห์ความต้องการของผู้ใช้ กำหนดกระบวนการทำงาน ข้อมูล และกฎทางธุรกิจ')
. bullet('ออกแบบสถาปัตยกรรมระบบ ฐานข้อมูล ส่วนติดต่อผู้ใช้ การเชื่อมต่อบริการ และมาตรการความมั่นคงปลอดภัย')
. para('2.2 งานพัฒนาระบบ', 'left', true, 29, 80, 50)
. bullet('ข้อมูลหลัก: บริษัท ผู้ใช้ ผู้ติดต่อ สินค้า คลังสินค้า โซน ทางเดิน ตำแหน่งจัดเก็บ และหน่วยนับ')
. bullet('สินค้าคงคลัง: รับเข้า จ่ายออก โอนย้าย ปรับปรุงยอด ตรวจนับ ยอดคงเหลือ และประวัติการเคลื่อนไหว')
. bullet('เอกสารธุรกิจ: Sales Order, Purchase Order, Return, Invoice และลำดับเลขที่เอกสาร')
. bullet('การเงินและบัญชี: รายรับ รายจ่าย สมุดรายวัน รายการบัญชี และรายงานที่ระบบรองรับ')
. bullet('Dashboard, รายงาน, การส่งออกข้อมูล, Barcode/Label และการแนบไฟล์')
. bullet('การยืนยันตัวตน การกำหนดบทบาท Owner/Admin/Staff/Viewer การจำกัดข้อมูลตามบริษัท และการควบคุม session')
. bullet('บริการแจ้งเตือนแบบเรียลไทม์และงานตามกำหนดเวลาด้วย Node.js/Socket.IO')
. para('2.3 งานทดสอบและส่งมอบ', 'left', true, 29, 80, 50)
. bullet('จัดทำและดำเนินการทดสอบตามความต้องการ บันทึกผล แก้ไขข้อบกพร่อง และทดสอบยืนยันผล')
. bullet('จัดเตรียมคู่มือผู้ใช้ คู่มือการติดตั้ง/ปฏิบัติการ และเอกสารบำรุงรักษา')
. bullet('จัดเตรียมหลักฐานการทวนสอบ การตรวจสอบความใช้ได้ และการยอมรับระบบ')
. para('3. ผลส่งมอบ', 'left', true, 32, 150, 80)
. para('ผลส่งมอบประกอบด้วยซอฟต์แวร์ BRN WMS ซอร์สโค้ด สคริปต์การติดตั้งและฐานข้อมูล การกำหนดค่า คู่มือ และ Work Products ของกระบวนการ Project Management และ Software Implementation ตาม ISO/IEC 29110 รวม 22 รายการ โดยจัดเก็บใน Project Repository ภายใต้การควบคุมเวอร์ชัน', 'both', false, 30, 0, 100, 720)
. para('4. ข้อยกเว้นและข้อสมมติ', 'left', true, 32, 120, 80)
. bullet('ไม่รวมการจัดหาเครื่องแม่ข่าย อุปกรณ์เครือข่าย เครื่องสแกน Barcode หรือบริการจากบุคคลภายนอก เว้นแต่ได้รับอนุมัติเพิ่มเติม')
. bullet('การย้ายข้อมูลเดิม การเชื่อมต่อ ERP/บริการภายนอก และการปรับแต่งนอกขอบเขตต้องผ่านกระบวนการ Change Request')
. bullet('ผู้มีส่วนเกี่ยวข้องต้องให้ข้อมูล ทบทวนเอกสาร และเข้าร่วมการทดสอบ/ยอมรับตามกำหนด')
. para('5. แผนงานและจุดควบคุม', 'left', true, 32, 150, 80)
. bullet('เริ่มต้นและวางแผนโครงการ: 5 มกราคม – 18 กุมภาพันธ์ 2569')
. bullet('พัฒนาและทดสอบภายใน: 19 กุมภาพันธ์ – 29 พฤษภาคม 2569')
. bullet('ทดสอบการยอมรับ จัดทำเอกสาร ส่งมอบ และปรับเสถียรภาพ: 30 พฤษภาคม – 14 สิงหาคม 2569')
. bullet('วันเสร็จสิ้นโครงการอย่างเป็นทางการ: 14 สิงหาคม 2569')
. para('6. เกณฑ์การยอมรับ', 'left', true, 32, 150, 80)
. bullet('ฟังก์ชันที่อยู่ในขอบเขตผ่าน Test Cases และเชื่อมโยงกับความต้องการใน Traceability Record')
. bullet('ข้อบกพร่องระดับร้ายแรงที่ขัดขวางการใช้งานได้รับการแก้ไขหรือมีแนวทางที่ผู้มีอำนาจยอมรับ')
. bullet('เอกสารการติดตั้ง การใช้งาน การปฏิบัติการ และการบำรุงรักษาพร้อมใช้งาน')
. bullet('ผลการ Verification, Validation และ Acceptance ได้รับการทบทวนและลงนามโดยผู้มีอำนาจ')
. para('7. การบริหารการเปลี่ยนแปลง', 'left', true, 32, 150, 80)
. para('การเปลี่ยนแปลงขอบเขต กำหนดการ หรือผลส่งมอบต้องบันทึกใน Change Report ประเมินผลกระทบ และได้รับอนุมัติก่อนดำเนินการ การแก้ไขข้อบกพร่องให้บันทึกใน Correction Register และเชื่อมโยงกับหลักฐานการทดสอบที่เกี่ยวข้อง', 'both', false, 30, 0, 120, 720)
. para('จึงจัดทำ Statement of Work ฉบับนี้เพื่อใช้เป็นกรอบดำเนินงานและขอให้ผู้เกี่ยวข้องพิจารณาอนุมัติตามอำนาจหน้าที่', 'both', false, 30, 80, 240, 720)
. para('ลงชื่อ ____________________________________', 'center', false, 30, 240, 20)
. para('(คุณเสรี วิริยะสกุลธรณ์)', 'center', false, 30, 0, 20)
. para('กรรมการผู้จัดการ · Project Sponsor / Customer Representative / Authorized Approver', 'center', false, 26, 0, 20)
. para('บริษัท บี.อาร์.เอ็น. เอ็นเตอร์ไพรส์ จำกัด', 'center', false, 30, 0, 20)
. para('วันที่ ____________________________________', 'center', false, 30, 0, 80);
$document = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
. '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
. '<w:body>' . $body
. '<w:sectPr><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="1080" w:right="1080" w:bottom="1080" w:left="1260" w:header="500" w:footer="500"/>'
. '<w:footerReference w:type="default" r:id="rId1"/></w:sectPr></w:body></w:document>';
$contentTypes = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
. '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
. '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
. '<Default Extension="xml" ContentType="application/xml"/>'
. '<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>'
. '<Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/>'
. '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>'
. '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>'
. '</Types>';
$rels = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
. '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
. '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>'
. '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>'
. '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>'
. '</Relationships>';
$documentRels = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
. '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
. '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/>'
. '</Relationships>';
$footer = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
. '<w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'
. '<w:p><w:pPr><w:jc w:val="center"/></w:pPr>'
. run('200-WMS-26-001-00 · Statement of Work · V1.0 · Final', false, 18)
. '</w:p></w:ftr>';
$core = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
. '<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" '
. 'xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
. '<dc:title>200-WMS-26-001-00 Statement of Work</dc:title><dc:subject>ISO/IEC 29110 Project Management Work Product</dc:subject>'
. '<dc:creator>BRN WMS Project Team</dc:creator><cp:keywords>ISO/IEC 29110; Statement of Work; BRN WMS</cp:keywords>'
. '<dc:description>Editable final document ready for authorized approval and signature.</dc:description>'
. '<dcterms:created xsi:type="dcterms:W3CDTF">2026-01-05T00:00:00+07:00</dcterms:created>'
. '<dcterms:modified xsi:type="dcterms:W3CDTF">2026-08-17T00:00:00+07:00</dcterms:modified>'
. '</cp:coreProperties>';
$app = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
. '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" '
. 'xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">'
. '<Application>Microsoft Office Word</Application><Company>B.R.N. Enterprise Co., Ltd.</Company><AppVersion>16.0000</AppVersion>'
. '</Properties>';
$zip = new ZipArchive();
if ($zip->open($output, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
throw new RuntimeException('Unable to create ' . $output);
}
$zip->addFromString('[Content_Types].xml', $contentTypes);
$zip->addFromString('_rels/.rels', $rels);
$zip->addFromString('word/document.xml', $document);
$zip->addFromString('word/_rels/document.xml.rels', $documentRels);
$zip->addFromString('word/footer1.xml', $footer);
$zip->addFromString('docProps/core.xml', $core);
$zip->addFromString('docProps/app.xml', $app);
$zip->close();
echo $output . PHP_EOL;
require __DIR__ . '/normalize_document_dates.php';
-148
View File
@@ -1,148 +0,0 @@
<?php
declare(strict_types=1);
$output = __DIR__ . '/../sdlc/1-PM Process (10 Work Product)/2.Project Plan/1-Work Schedule/'
. '200-WMS-26-001-00 Work Schedule 25690105 V1.0 Final.xlsx';
$headers = ['No.', 'Phase', 'Task', 'Details / Basis', 'Responsible Role', 'Start', 'Finish', 'Duration (days)', 'Deliverable / Evidence', 'Status', 'Remarks'];
$rows = [
['1.1', 'Initiation', 'Identify project need', 'Establish need for centralized warehouse, inventory, order, and accounting control.', 'Project Sponsor / Project Manager', '05-Jan-2026', '09-Jan-2026', '5', 'Statement of Work', 'Completed', 'Reconstructed planning activity'],
['1.2', 'Initiation', 'Identify stakeholders and objectives', 'Identify sponsor, operational users, system administrator, development, and approval roles.', 'Project Manager', '05-Jan-2026', '16-Jan-2026', '10', 'Stakeholder and objective records', 'Completed', 'Reconstructed planning activity'],
['1.3', 'Initiation', 'Approve project scope', 'Confirm project boundaries, assumptions, deliverables, and acceptance approach.', 'Project Sponsor', '19-Jan-2026', '23-Jan-2026', '5', 'Approved Statement of Work', 'Pending signature', 'Authority signature required'],
['2.1', 'Planning', 'Collect customer requirements', 'Document functional, data, security, operational, and quality requirements.', 'System Analyst / Customer Representatives', '12-Jan-2026', '06-Feb-2026', '20', 'Customer Requirements', 'Completed', 'Reconstructed from implemented system'],
['2.2', 'Planning', 'Prepare Software Project Plan', 'Define lifecycle, resources, risks, repository, configuration, communication, and controls.', 'Project Manager', '26-Jan-2026', '13-Feb-2026', '15', 'Software Project Plan', 'Completed', 'Reconstructed project plan'],
['2.3', 'Planning', 'Baseline requirements and schedule', 'Review initial requirements, priorities, milestones, and work-product responsibilities.', 'Project Manager / System Analyst', '16-Feb-2026', '18-Feb-2026', '3', 'Baseline plan and requirements', 'Completed', 'Development begins 19-Feb-2026'],
['3.1', 'Execution', 'Initialize WMS application', 'Create initial PHP application repository and baseline structure.', 'System Analyst / Developer', '19-Feb-2026', '25-Feb-2026', '5', 'Git commits 9a50080–1843308', 'Completed', 'Git evidenced'],
['3.2', 'Execution', 'Security and database foundation', 'Configuration protection, security audit actions, stock database design.', 'Developer', '09-Mar-2026', '17-Mar-2026', '7', 'Git commits 93d903c–a4f474b', 'Completed', 'Git evidenced'],
['3.3', 'Execution', 'Inventory and warehouse modules', 'Warehouse capacity, product, storage/bin, lot, serial, expiry, stock movements, and reports.', 'Developer', '09-Apr-2026', '29-Apr-2026', '15', 'Inventory, ICS, dashboard and report modules', 'Completed', 'Git evidenced'],
['3.4', 'Execution', 'Authentication and onboarding', 'Login, registration, onboarding, password controls, and role-based access.', 'Developer', '28-Apr-2026', '12-May-2026', '11', 'Authentication and user-management modules', 'Completed', 'Git evidenced'],
['3.5', 'Execution', 'Order and barcode workflows', 'Orders, returns, invoices, switchable warehouse layers, barcode labels, and scanning.', 'Developer', '02-May-2026', '08-May-2026', '5', 'Order and barcode modules', 'Completed', 'Git evidenced'],
['3.6', 'Execution', 'Production preparation and setup', 'Dynamic base URL, automated database setup, recovery flow, and production preparation.', 'Developer', '11-May-2026', '13-May-2026', '3', 'Setup and configuration implementation', 'Completed', 'Git evidenced'],
['3.7', 'Execution', 'Accounting and finance workflows', 'Chart of accounts, GL, journals, reports, billing, payment and receipt workflows.', 'Developer', '13-May-2026', '23-May-2026', '9', 'Accounting and finance modules', 'Completed', 'Git evidenced'],
['3.8', 'Execution', 'Real-time services and scheduled jobs', 'Socket notifications, stock/GL aggregates, low-stock and overdue-invoice schedules.', 'Developer', '22-May-2026', '27-May-2026', '4', 'Node.js service and cron jobs', 'Completed', 'Git evidenced'],
['3.9', 'Execution', 'Security hardening and lifecycle review', 'Role guards, tenant scoping, document flows, transaction limits, and correction closure.', 'Developer / Reviewer', '21-May-2026', '28-May-2026', '6', 'Security and lifecycle review commits', 'Completed', 'Git evidenced'],
['3.10', 'Execution', 'Refactor and development baseline', 'Remove redundancy and establish substantially complete development baseline.', 'Developer', '29-May-2026', '29-May-2026', '1', 'Git commit a0677d6', 'Completed', 'Development substantially complete'],
['4.1', 'Control', 'Maintain progress and meeting records', 'Track progress, issues, decisions, risks, and corrective actions throughout the project.', 'Project Manager / Document Control', '05-Jan-2026', '14-Aug-2026', '160', 'Progress Status, Meeting Records, Correction Register', 'Completed', 'Records require evidence review'],
['4.2', 'Control', 'Configuration and repository control', 'Control source, baselines, document versions, configuration, and backups.', 'Configuration Manager', '19-Feb-2026', '14-Aug-2026', '127', 'Git repository and Software Configuration', 'Completed', 'Git repository evidenced'],
['4.3', 'Verification', 'Verify requirements, design, and implementation', 'Conduct reviews and tests; link requirements, design, code, and test evidence.', 'Reviewer / Tester', '30-May-2026', '31-Jul-2026', '45', 'Verification Results and Traceability Record', 'Completed', 'Formal approval pending'],
['4.4', 'Validation', 'Customer-oriented system validation', 'Validate operational workflows and quality attributes against intended use.', 'Customer Representatives / Tester', '01-Jun-2026', '07-Aug-2026', '50', 'Validation Results and Test Report', 'Completed', 'Formal approval pending'],
['4.5', 'Documentation', 'Prepare operational documentation', 'Prepare user, operation, configuration, deployment, and maintenance guidance.', 'Developer / Document Control', '01-Jun-2026', '07-Aug-2026', '50', 'User Documentation, Operation Guide, Maintenance Documentation', 'Completed', 'Reconstructed documentation period'],
['4.6', 'Stabilization', 'Configuration and login corrections', 'Correct login and environment configuration issues identified after baseline.', 'Developer', '03-Aug-2026', '03-Aug-2026', '1', 'Git commit b2c4374', 'Completed', 'Git evidenced'],
['4.7', 'Stabilization', 'Prepare demonstration data', 'Populate controlled demonstration data for validation and handover support.', 'Developer / Tester', '14-Aug-2026', '14-Aug-2026', '1', 'Git commit dd48a8b', 'Completed', 'Git evidenced'],
['5.1', 'Closure', 'Final repository and work-product review', 'Confirm required work products, traceability, configuration items, and unresolved actions.', 'Project Manager / Document Control', '10-Aug-2026', '14-Aug-2026', '5', 'Repository review and List of Evidence', 'Completed', 'Approval records pending'],
['5.2', 'Closure', 'Acceptance and project closure', 'Obtain authorized acceptance and record project closure and follow-up actions.', 'Project Sponsor / Project Manager', '14-Aug-2026', '14-Aug-2026', '1', 'Acceptance Report and closure record', 'Pending signature', 'Authority signature required'],
];
function esc(string $value): string
{
return htmlspecialchars($value, ENT_XML1 | ENT_QUOTES, 'UTF-8');
}
function colName(int $number): string
{
$name = '';
while ($number > 0) {
$number--;
$name = chr(65 + ($number % 26)) . $name;
$number = intdiv($number, 26);
}
return $name;
}
function cell(string $reference, string $value, int $style): string
{
return '<c r="' . $reference . '" t="inlineStr" s="' . $style . '"><is><t xml:space="preserve">' . esc($value) . '</t></is></c>';
}
$sheetRows = [];
$sheetRows[] = '<row r="1" ht="30" customHeight="1">'
. cell('A1', 'Document No: Work Schedule', 1)
. cell('B1', 'Project: BRN WMS', 1)
. cell('C1', 'Project Code: 200-WMS-26-001-00', 1)
. cell('D1', 'Period: 05-Jan-2026 to 14-Aug-2026', 1)
. cell('E1', 'Release: 05/01/26 V1.0 Final', 1)
. '</row>';
$sheetRows[] = '<row r="2" ht="26" customHeight="1">';
foreach ($headers as $index => $header) {
$sheetRows[1] .= cell(colName($index + 1) . '2', $header, 2);
}
$sheetRows[1] .= '</row>';
foreach ($rows as $rowIndex => $row) {
$excelRow = $rowIndex + 3;
$xml = '<row r="' . $excelRow . '" ht="42" customHeight="1">';
foreach ($row as $columnIndex => $value) {
$style = $columnIndex === 9
? ($value === 'Completed' ? 4 : 5)
: ($columnIndex === 0 || $columnIndex >= 5 && $columnIndex <= 7 ? 3 : 6);
$xml .= cell(colName($columnIndex + 1) . $excelRow, $value, $style);
}
$sheetRows[] = $xml . '</row>';
}
$lastRow = count($rows) + 2;
$sheet = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
. '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
. '<sheetPr><pageSetUpPr fitToPage="1"/></sheetPr><dimension ref="A1:K' . $lastRow . '"/>'
. '<sheetViews><sheetView workbookViewId="0"><pane ySplit="2" topLeftCell="A3" activePane="bottomLeft" state="frozen"/></sheetView></sheetViews>'
. '<cols><col min="1" max="1" width="7" customWidth="1"/><col min="2" max="2" width="14" customWidth="1"/>'
. '<col min="3" max="3" width="29" customWidth="1"/><col min="4" max="4" width="48" customWidth="1"/>'
. '<col min="5" max="5" width="28" customWidth="1"/><col min="6" max="7" width="14" customWidth="1"/>'
. '<col min="8" max="8" width="14" customWidth="1"/><col min="9" max="9" width="34" customWidth="1"/>'
. '<col min="10" max="10" width="18" customWidth="1"/><col min="11" max="11" width="28" customWidth="1"/></cols>'
. '<sheetData>' . implode('', $sheetRows) . '</sheetData>'
. '<autoFilter ref="A2:K' . $lastRow . '"/><mergeCells count="5"><mergeCell ref="A1:A1"/><mergeCell ref="B1:B1"/><mergeCell ref="C1:C1"/><mergeCell ref="D1:D1"/><mergeCell ref="E1:K1"/></mergeCells>'
. '<pageMargins left="0.25" right="0.25" top="0.4" bottom="0.4" header="0.2" footer="0.2"/>'
. '<pageSetup orientation="landscape" paperSize="9" fitToWidth="1" fitToHeight="0"/>'
. '<printOptions horizontalCentered="1"/><headerFooter><oddFooter>&amp;LISO/IEC 29110&amp;C200-WMS-26-001-00&amp;RPage &amp;P of &amp;N</oddFooter></headerFooter>'
. '</worksheet>';
$styles = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
. '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
. '<fonts count="4"><font><sz val="10"/><name val="Arial"/></font><font><b/><color rgb="FFFFFFFF"/><sz val="11"/><name val="Arial"/></font>'
. '<font><b/><color rgb="FFFFFFFF"/><sz val="10"/><name val="Arial"/></font><font><sz val="9"/><name val="Arial"/></font></fonts>'
. '<fills count="5"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill>'
. '<fill><patternFill patternType="solid"><fgColor rgb="FF075985"/></patternFill></fill>'
. '<fill><patternFill patternType="solid"><fgColor rgb="FF0E7490"/></patternFill></fill>'
. '<fill><patternFill patternType="solid"><fgColor rgb="FFFEF3C7"/></patternFill></fill></fills>'
. '<borders count="2"><border/><border><left style="thin"><color rgb="FFB7C9D6"/></left><right style="thin"><color rgb="FFB7C9D6"/></right><top style="thin"><color rgb="FFB7C9D6"/></top><bottom style="thin"><color rgb="FFB7C9D6"/></bottom></border></borders>'
. '<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>'
. '<cellXfs count="7"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>'
. '<xf numFmtId="0" fontId="1" fillId="2" borderId="1" xfId="0" applyAlignment="1"><alignment vertical="center" wrapText="1"/></xf>'
. '<xf numFmtId="0" fontId="2" fillId="2" borderId="1" xfId="0" applyAlignment="1"><alignment horizontal="center" vertical="center" wrapText="1"/></xf>'
. '<xf numFmtId="0" fontId="3" fillId="0" borderId="1" xfId="0" applyAlignment="1"><alignment horizontal="center" vertical="top" wrapText="1"/></xf>'
. '<xf numFmtId="0" fontId="3" fillId="3" borderId="1" xfId="0" applyAlignment="1"><alignment horizontal="center" vertical="top" wrapText="1"/></xf>'
. '<xf numFmtId="0" fontId="3" fillId="4" borderId="1" xfId="0" applyAlignment="1"><alignment horizontal="center" vertical="top" wrapText="1"/></xf>'
. '<xf numFmtId="0" fontId="3" fillId="0" borderId="1" xfId="0" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf></cellXfs>'
. '<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles></styleSheet>';
$contentTypes = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
. '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/>'
. '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'
. '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
. '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>'
. '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>'
. '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/></Types>';
$rootRels = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
. '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>'
. '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>'
. '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/></Relationships>';
$workbook = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
. '<sheets><sheet name="Work Schedule" sheetId="1" r:id="rId1"/></sheets><definedNames><definedName name="_xlnm.Print_Area" localSheetId="0">\'Work Schedule\'!$A$1:$K$' . $lastRow . '</definedName></definedNames></workbook>';
$workbookRels = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
. '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>'
. '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>';
$core = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dc:title>200-WMS-26-001-00 Work Schedule</dc:title><dc:creator>BRN WMS Project Team</dc:creator><dc:description>ISO/IEC 29110 reconstructed final project schedule ready for authorization.</dc:description><dcterms:created xsi:type="dcterms:W3CDTF">2026-01-05T00:00:00+07:00</dcterms:created></cp:coreProperties>';
$app = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"><Application>Microsoft Excel</Application><Company>B.R.N. Enterprise Co., Ltd.</Company></Properties>';
$zip = new ZipArchive();
if ($zip->open($output, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
throw new RuntimeException('Cannot create ' . $output);
}
foreach (['[Content_Types].xml' => $contentTypes, '_rels/.rels' => $rootRels, 'xl/workbook.xml' => $workbook, 'xl/_rels/workbook.xml.rels' => $workbookRels, 'xl/worksheets/sheet1.xml' => $sheet, 'xl/styles.xml' => $styles, 'docProps/core.xml' => $core, 'docProps/app.xml' => $app] as $name => $contents) {
$zip->addFromString($name, $contents);
}
$zip->close();
echo $output . PHP_EOL;
require __DIR__ . '/normalize_document_dates.php';
-99
View File
@@ -1,99 +0,0 @@
<?php
declare(strict_types=1);
$root = __DIR__ . '/../sdlc';
$english = [
'January'=>'01','February'=>'02','March'=>'03','April'=>'04','May'=>'05','June'=>'06',
'July'=>'07','August'=>'08','September'=>'09','October'=>'10','November'=>'11','December'=>'12',
'Jan'=>'01','Feb'=>'02','Mar'=>'03','Apr'=>'04','Jun'=>'06','Jul'=>'07','Aug'=>'08','Sep'=>'09','Oct'=>'10','Nov'=>'11','Dec'=>'12',
];
$thai = ['มกราคม'=>'01','กุมภาพันธ์'=>'02','มีนาคม'=>'03','เมษายน'=>'04','พฤษภาคม'=>'05','มิถุนายน'=>'06','กรกฎาคม'=>'07','สิงหาคม'=>'08','กันยายน'=>'09','ตุลาคม'=>'10','พฤศจิกายน'=>'11','ธันวาคม'=>'12'];
function normalDate(int|string $day, string $month, int|string $year, array $english, array $thai): string
{
$monthNo = $english[$month] ?? $thai[$month] ?? null;
if ($monthNo === null) return (string)$day . ' ' . $month . ' ' . (string)$year;
$yearNo = (int)$year;
if ($yearNo >= 2400) $yearNo -= 543;
return sprintf('%02d/%s/%02d', (int)$day, $monthNo, $yearNo % 100);
}
function normalizeDates(string $text, array $english, array $thai): string
{
$months = implode('|', array_map(static fn($v) => preg_quote($v, '/'), array_keys($english + $thai)));
// Compact Buddhist document/report dates, e.g. 25690817.
$text = preg_replace_callback('/\b(25\d{2})(\d{2})(\d{2})\b/', static function ($m) {
return sprintf('%02d/%02d/%02d', (int)$m[3], (int)$m[2], ((int)$m[1] - 543) % 100);
}, $text) ?? $text;
// Shared-month range: 11–13 May 2026.
$text = preg_replace_callback('/\b(\d{1,2})\s*[–-]\s*(\d{1,2})\s+('.$months.')\s+(20\d{2}|25\d{2})\b/u', static function ($m) use ($english,$thai) {
return normalDate($m[1],$m[3],$m[4],$english,$thai).'–'.normalDate($m[2],$m[3],$m[4],$english,$thai);
}, $text) ?? $text;
// Two-month range with a shared year: 5 January–14 August 2026.
$text = preg_replace_callback('/\b(\d{1,2})\s+('.$months.')\s*[–-]\s*(\d{1,2})\s+('.$months.')\s+(20\d{2}|25\d{2})\b/u', static function ($m) use ($english,$thai) {
return normalDate($m[1],$m[2],$m[5],$english,$thai).'–'.normalDate($m[3],$m[4],$m[5],$english,$thai);
}, $text) ?? $text;
// Hyphenated two-month range: 19-Feb–29-May-2026.
$text = preg_replace_callback('/\b(\d{1,2})-('.$months.')\s*[–-]\s*(\d{1,2})-('.$months.')-(20\d{2}|25\d{2})\b/u', static function ($m) use ($english,$thai) {
return normalDate($m[1],$m[2],$m[5],$english,$thai).'–'.normalDate($m[3],$m[4],$m[5],$english,$thai);
}, $text) ?? $text;
// A full single date using a month name or abbreviation.
$text = preg_replace_callback('/\b(\d{1,2})(?:-|\s+)('.$months.')(?:-|\s+)(20\d{2}|25\d{2})\b/u', static function ($m) use ($english,$thai) {
return normalDate($m[1],$m[2],$m[3],$english,$thai);
}, $text) ?? $text;
// Period descriptions without a repeated year, all known to be in 2026.
$text = preg_replace_callback('/\b(\d{1,2})\s*[–-]\s*(\d{1,2})\s+('.$months.')(?=\s+(?:commits|accounting|May|April|January|February|March|June|July|August))/u', static function ($m) use ($english,$thai) {
return normalDate($m[1],$m[3],2026,$english,$thai).'–'.normalDate($m[2],$m[3],2026,$english,$thai);
}, $text) ?? $text;
// Commit-period shorthand: 30-Apr through 08-May.
$text = preg_replace_callback('/\b(\d{1,2})-('.$months.')\s+through\s+(\d{1,2})-('.$months.')\b/u', static function ($m) use ($english,$thai) {
return normalDate($m[1],$m[2],2026,$english,$thai).'–'.normalDate($m[3],$m[4],2026,$english,$thai);
}, $text) ?? $text;
return $text;
}
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS));
$updated = 0;
foreach ($iterator as $file) {
if (!$file->isFile() || !str_starts_with($file->getFilename(), '200-WMS-')) continue;
$path = $file->getPathname();
$extension = strtolower($file->getExtension());
if ($extension === 'md') {
$before = file_get_contents($path);
if ($before === false) continue;
$after = normalizeDates($before, $english, $thai);
if ($after !== $before) {
file_put_contents($path, $after);
$updated++;
}
continue;
}
if (!in_array($extension, ['docx','xlsx'], true)) continue;
$zip = new ZipArchive();
if ($zip->open($path) !== true) continue;
$changed = false;
for ($i=0; $i<$zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
if (!str_ends_with($name, '.xml')) continue;
$before = $zip->getFromIndex($i);
$after = normalizeDates($before, $english, $thai);
if ($after === $before) continue;
$dom = new DOMDocument();
if (!$dom->loadXML($after)) throw new RuntimeException("Invalid XML after date conversion: {$path}::{$name}");
$zip->addFromString($name, $after);
$changed = true;
}
$zip->close();
if ($changed) $updated++;
}
echo "Updated {$updated} BRN WMS document(s).\n";
@@ -1,7 +0,0 @@
<table class="document-control">
<tbody>
<tr><th>Document</th><td>{{documentType}}</td><th>Release</th><td>{{release}}</td></tr>
<tr><th>Project</th><td>{{projectName}}</td><th>Project code</th><td>{{projectCode}}</td></tr>
<tr><th>Project period</th><td colspan="3">{{projectPeriod}}</td></tr>
</tbody>
</table>
+1 -1
View File
@@ -1,4 +1,4 @@
<div style="border-top:1.5pt solid #1f2933; color:#52606d; display:flex; font-family:'BRN Thai',Arial,sans-serif; font-size:8pt; justify-content:space-between; margin:0 16mm; padding-top:2.5mm; width:calc(100% - 32mm);">
<span>ISO/IEC 29110-4-1:2018</span>
<span>{{footerNote}}</span>
<span>{{projectCode}}</span>
</div>
@@ -160,3 +160,19 @@ tr { break-inside: avoid; }
margin: 0;
padding: 0;
}
/* Signature blocks: heading and its table stay together, in the reference layout. */
.signature-block { break-inside: avoid; margin: 0 0 5mm; }
.signature-block h2.signature-heading { border-left: none; font-size: 11pt; margin: 5mm 0 2mm; padding-left: 0; }
/* Fixed columns so the signature grid always fits the text block width. */
table.signature-table { table-layout: fixed; }
table.signature-table td { height: 13mm; }
table.signature-table th.tight,
table.signature-table td.tight { white-space: normal; width: auto; }
table.signature-table th:nth-child(1), table.signature-table td:nth-child(1) { width: 32%; }
table.signature-table th:nth-child(2), table.signature-table td:nth-child(2) { width: 26%; }
table.signature-table th:nth-child(3), table.signature-table td:nth-child(3) { width: 22%; }
table.signature-table th:nth-child(4), table.signature-table td:nth-child(4) { width: 20%; }
/* The document-control block repeats its label column, so labels must not stretch. */
.document-control th { width: 1%; }
+54 -29
View File
@@ -13,6 +13,9 @@ const escapeHtml = (value) => value
.replaceAll('>', '&gt;');
const renderInline = (value) => escapeHtml(value)
// A literal <br> is the only markup allowed through, so a table cell can list
// several files on separate lines the way the reference package does.
.replaceAll('&lt;br&gt;', '<br>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/_{3,}/g, '<span class="approval-block__line"></span>')
@@ -44,6 +47,23 @@ function columnAlignment(separatorCells) {
});
}
function isControlBlock(lines) {
return tableCells(lines[0] ?? '')[0] === 'Document No';
}
// The document-control block is a label/value grid, not a data table: odd cells are
// labels, even cells are values, and a row with one pair spans the full width.
function renderControlBlock(lines) {
const rows = lines.map(tableCells);
const body = rows.map((cells) => {
const pairs = [];
for (let i = 0; i < cells.length; i += 2) pairs.push([cells[i], cells[i + 1] ?? '']);
const span = pairs.length === 1 ? ' colspan="3"' : '';
return `<tr>${pairs.map(([label, value]) => `<th>${renderInline(label)}</th><td${span}>${renderInline(value)}</td>`).join('')}</tr>`;
}).join('');
return `<table class="document-control"><tbody>${body}</tbody></table>`;
}
function renderTable(lines, className = '') {
const rows = lines.map(tableCells);
const header = rows[0];
@@ -92,6 +112,13 @@ function markdownToHtml(markdown) {
if (isTableLine(line)) {
const table = [];
while (index < lines.length && isTableLine(lines[index])) table.push(lines[index++]);
if (isControlBlock(table)) { output.push(renderControlBlock(table)); continue; }
const previous = output[output.length - 1] ?? '';
// Keep a signature heading and its table together on one page.
if (/^<h2 class="signature-heading">/.test(previous)) {
output.push(`<div class="signature-block">${output.pop()}${renderTable(table, 'signature-table')}</div>`);
continue;
}
output.push(renderTable(table));
continue;
}
@@ -99,7 +126,9 @@ function markdownToHtml(markdown) {
const heading = line.match(/^(#{1,3})\s+(.+)$/);
if (heading) {
const level = heading[1].length;
output.push(`<h${level}>${renderInline(heading[2])}</h${level}>`);
const signature = level === 2 && /^ผู้(จัดทำเอกสาร|ตรวจสอบเอกสาร|อนุมัติ)/.test(heading[2]);
const attr = signature ? ' class="signature-heading"' : '';
output.push(`<h${level}${attr}>${renderInline(heading[2])}</h${level}>`);
index += 1;
continue;
}
@@ -136,32 +165,26 @@ function markdownToHtml(markdown) {
function extractDocument(markdown) {
const titleMatch = markdown.match(/^#\s+(.+)\n+/m);
const title = titleMatch?.[1] ?? 'BRN WMS';
const afterTitle = markdown.slice((titleMatch?.index ?? 0) + (titleMatch?.[0].length ?? 0));
const lines = afterTitle.split('\n');
const metadata = {};
let bodyStart = 0;
const footer = markdown.match(/^<!--\s*footer:\s*(.+?)\s*-->\s*$/m)?.[1] ?? '';
const body = markdown
.slice((titleMatch?.index ?? 0) + (titleMatch?.[0].length ?? 0))
.replace(/^<!--\s*footer:.*?-->\s*$/m, '');
// The control block carries the document identity used by the header and footer.
const metadata = {};
const lines = body.split('\n');
for (let index = 0; index < lines.length; index += 1) {
if (!isTableLine(lines[index])) continue;
const table = [];
while (index < lines.length && isTableLine(lines[index])) table.push(lines[index++]);
const candidate = {};
const dataStart = isSeparator(tableCells(table[1] ?? '')) ? 2 : 1;
for (const row of table.slice(dataStart).map(tableCells)) {
if (row.length >= 2) candidate[row[0]] = row[1];
if (!isControlBlock(table)) { index -= 1; continue; }
for (const cells of table.map(tableCells)) {
for (let i = 0; i < cells.length; i += 2) if (cells[i]) metadata[cells[i]] = cells[i + 1] ?? '';
}
if (candidate.Document && candidate.Project) {
Object.assign(metadata, candidate);
bodyStart = index;
break;
}
index -= 1;
break;
}
return { title, metadata, body: lines.slice(bodyStart).join('\n') };
return { title, footer, metadata, body };
}
async function main() {
@@ -176,27 +199,29 @@ async function main() {
fs.readFile(path.join(root, 'app/assets/images/logo.svg')),
fs.readFile(path.join(assets, 'LeelawadeeUI.ttf'))
]);
const { title, metadata, body } = extractDocument(markdown);
const hasWideTable = markdown.split('\n').some((line) => isTableLine(line) && tableCells(line).length >= 8);
const isLandscape = metadata.Document === 'Work Schedule' || title === 'Work Schedule' || hasWideTable;
const { title, footer, metadata, body } = extractDocument(markdown);
// Wide reference tables (schedule, register, test cases, traceability) need landscape;
// ordinary 8-column status tables still read well in portrait.
const widestTable = Math.max(0, ...markdown.split('\n').filter(isTableLine).map((line) => tableCells(line).length));
const LANDSCAPE_TITLES = new Set(['Work Schedule', 'Traceability Record', 'Traceability Record Table']);
const isLandscape = LANDSCAPE_TITLES.has(title) || widestTable >= 9;
const values = {
logoPath: `data:image/svg+xml;base64,${logo.toString('base64')}`,
documentTitle: title,
documentType: metadata.Document ?? title,
release: metadata.Release ?? '',
projectName: metadata.Project ?? 'BRN WMS',
projectCode: metadata['Project code'] ?? '200-WMS-26-001-00',
projectPeriod: metadata['Project period'] ?? '05/01/26–24/08/26',
documentType: metadata['Document No'] ?? title,
release: metadata['Release, Version, By:'] ?? '',
projectName: metadata['Project Name'] ?? 'BRN WMS',
projectCode: metadata['Project Code'] ?? '200-WMS-26-001-00',
footerNote: footer ? `ISO/IEC 29110-4-1:2018 ${footer}` : 'ISO/IEC 29110-4-1:2018',
pageNumber: '<span class="pageNumber"></span>',
totalPages: '<span class="totalPages"></span>'
};
const control = await fs.readFile(path.join(assets, 'document-control.html'), 'utf8');
const pdfFooter = applyTemplate(footerTemplate, values);
const fontFace = `@font-face { font-family: "BRN Thai"; src: url(data:font/ttf;base64,${thaiFont.toString('base64')}) format("truetype"); font-weight: 400 700; }`;
const pageLayout = isLandscape
? '@page { size: A4 landscape; margin: 18mm 16mm 28mm; }'
: '';
const html = `<!doctype html><html><head><meta charset="utf-8"><style>${fontFace}${css}${pageLayout}</style></head><body><main class="delivery-document">${applyTemplate(headerTemplate, values)}${applyTemplate(control, values)}${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">${applyTemplate(headerTemplate, values)}${markdownToHtml(body)}</main></body></html>`;
await fs.mkdir(path.dirname(destination), { recursive: true });
const browser = await chromium.launch({ headless: true });
+6 -19
View File
@@ -19,20 +19,10 @@ function isSeparator(cells) {
function extractDocumentContent(markdown) {
const titleMatch = markdown.match(/^#\s+.+\n+/m);
const afterTitle = markdown.slice((titleMatch?.index ?? 0) + (titleMatch?.[0].length ?? 0));
const lines = afterTitle.split('\n');
for (let index = 0; index < lines.length; index += 1) {
if (!isTableLine(lines[index])) continue;
const table = [];
while (index < lines.length && isTableLine(lines[index])) table.push(lines[index++]);
const dataStart = isSeparator(tableCells(table[1] ?? '')) ? 2 : 1;
const fields = Object.fromEntries(table.slice(dataStart).map(tableCells).filter((row) => row.length >= 2));
if (fields.Document && fields.Project) return { body: lines.slice(index).join('\n'), fields };
index -= 1;
}
return { body: afterTitle, fields: {} };
const body = markdown
.slice((titleMatch?.index ?? 0) + (titleMatch?.[0].length ?? 0))
.replace(/^<!--\s*footer:.*?-->\s*$/m, '');
return { body, fields: {} };
}
function words(text) {
@@ -69,11 +59,8 @@ async function main() {
const relative = path.relative(sourceRoot, source);
const pdf = path.join(deliveryRoot, relative.replace(/\.md$/, '.pdf'));
const [markdown, extracted] = await Promise.all([fs.readFile(source, 'utf8'), pdfText(pdf)]);
const { body, fields } = extractDocumentContent(markdown);
const controlText = ['Document', 'Project', 'Project code', 'Project period', 'Release']
.map((field) => fields[field] ?? '')
.join('\n');
const expected = words(`${body}\n${controlText}`);
const { body } = extractDocumentContent(markdown);
const expected = words(body);
const actual = compact(extracted);
const missing = [...expected].filter((word) => !actual.includes(word));
if (missing.length) failures.push(`${relative}: missing ${missing.slice(0, 12).join(', ')}${missing.length > 12 ? ', …' : ''}`);
+31
View File
@@ -0,0 +1,31 @@
# SDLC work-product seed
Regenerates every ISO/IEC 29110 work product under `sdlc/` from controlled project
data, in the layout of the audited `200-TAS-25-001-00` reference package: Thai
section headings, a `Document No` control block, and Secretary / Reviewer /
Approval signature tables on every document.
`sdlc/` is generated output. Edit the data files here, never the Markdown.
## Files
| File | Contents |
|---|---|
| `project.mjs` | People, dates, project code, scope, deliverables, Customer Requirements (CR01–CR14) |
| `engineering.mjs` | Software Requirements (SR01–SR09), Software Units, Test Cases, and the CR→SR→Unit→Test mapping |
| `history.mjs` | Work Schedule, reporting periods, meetings, Correction Register, Change Reports |
| `lib.mjs` | Buddhist-calendar dates, Markdown tables, signature blocks |
| `format.mjs` | The shared work-product scaffold |
| `docs-*.mjs` | One generator per work product |
| `build.mjs` | Writes all documents and checks no folder is left empty |
## Usage
```bash
node scripts/sdlc-seed/build.mjs # regenerate sdlc/ only
bash scripts/build-sdlc-delivery.sh # seed, render to PDF, then verify
```
The traceability chain is validated at import time: every Customer Requirement
must resolve to existing Software Requirements, Software Units, and a Test Case,
or the build fails.
+86
View File
@@ -0,0 +1,86 @@
// Regenerate every ISO/IEC 29110 work product under sdlc/ from the controlled project data.
// Usage: node scripts/sdlc-seed/build.mjs
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import * as pm1 from './docs-pm1.mjs';
import * as pm2 from './docs-pm2.mjs';
import * as si from './docs-si.mjs';
import * as si2 from './docs-si2.mjs';
import * as other from './docs-other.mjs';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const target = path.join(root, 'sdlc');
// Folders that must exist even when a work product is evidence rather than a document.
const FOLDERS = [
'1-PM Process (10 Work Product)/1.Statement of Work',
'1-PM Process (10 Work Product)/2.Project Plan/1-Work Schedule',
'1-PM Process (10 Work Product)/2.Project Plan/2-Software Project Plan',
'1-PM Process (10 Work Product)/2.Project Plan/3-Customer Requirement',
'1-PM Process (10 Work Product)/3.Progress Status Record',
'1-PM Process (10 Work Product)/4.Correction Register',
'1-PM Process (10 Work Product)/5.Acceptance Report',
'1-PM Process (10 Work Product)/6.Change Report',
'1-PM Process (10 Work Product)/7.Meeting Record',
'1-PM Process (10 Work Product)/8.Software Configuration',
'1-PM Process (10 Work Product)/9.Project Repository',
'1-PM Process (10 Work Product)/10.Project Repository (Backup)',
'2-SI Process (12 Work Product)/11.Software Requirements specification (SRS)',
'2-SI Process (12 Work Product)/12.Software design',
'2-SI Process (12 Work Product)/13.Traceability record',
'2-SI Process (12 Work Product)/14.Software Components',
'2-SI Process (12 Work Product)/15.Test Cases and Test Procedures',
'2-SI Process (12 Work Product)/16.Test Report',
'2-SI Process (12 Work Product)/17.Software',
'2-SI Process (12 Work Product)/18.Software User Documentation',
'2-SI Process (12 Work Product)/19.Product Operation Guide',
'2-SI Process (12 Work Product)/20.Maintenance Documentation',
'2-SI Process (12 Work Product)/21.Verification Result',
'2-SI Process (12 Work Product)/22.Validation Result',
'3-Other Document',
];
function collect() {
const docs = [
pm1.statementOfWork(), pm1.workSchedule(), pm1.softwareProjectPlan(), pm1.customerRequirements(),
...pm2.progressStatusRecords(), pm2.correctionRegister(), pm2.acceptanceReport(),
...pm2.changeReports(), ...pm2.meetingRecords(),
pm2.softwareConfiguration(), pm2.projectRepository(), pm2.projectRepositoryBackup(),
si.softwareRequirements(), si.softwareDesign(), si.traceabilityRecord(), si.softwareComponents(),
si.testCases(), si.testReport(), si.software(),
si2.userDocumentation(), si2.operationGuide(), si2.maintenanceDocumentation(),
...si2.verificationResults(), si2.validationResults(),
other.stakeholderRegister(), other.projectCharter(), other.traceabilityTable(), other.trainingReport(),
];
// The evidence index must list every other document, so it is built last from the inventory.
docs.push(other.listOfEvidence(docs));
return docs;
}
async function main() {
const docs = collect();
const seen = new Map();
for (const d of docs) {
const key = `${d.dir}/${d.name}`;
if (seen.has(key)) throw new Error(`Duplicate document name: ${key}`);
seen.set(key, d);
if (!FOLDERS.includes(d.dir)) throw new Error(`Unknown folder for ${d.name}: ${d.dir}`);
}
await fs.rm(target, { recursive: true, force: true });
for (const folder of FOLDERS) await fs.mkdir(path.join(target, folder), { recursive: true });
for (const d of docs) await fs.writeFile(path.join(target, d.dir, `${d.name}.md`), d.md, 'utf8');
const empty = [];
for (const folder of FOLDERS) {
const entries = await fs.readdir(path.join(target, folder));
if (!entries.some((e) => e.endsWith('.md'))) empty.push(folder);
}
if (empty.length) throw new Error(`Folders with no work product:\n- ${empty.join('\n- ')}`);
console.log(`Generated ${docs.length} work products across ${FOLDERS.length} folders.`);
}
main().catch((error) => { console.error(error); process.exit(1); });
+278
View File
@@ -0,0 +1,278 @@
// 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 { SRS, UNITS, TEST_CASES, traceOf, srTopic, unitTopic } from './engineering.mjs';
import { CORRECTIONS, CHANGES, PERIODS, MEETINGS, TASKS } from './history.mjs';
import { bud, thLong, thRangeLong, table, bullets, numbered, daysBetween } from './lib.mjs';
import { workProduct, head } from './format.mjs';
const OTHER = '3-Other Document';
/** Stakeholder Register. */
export function stakeholderRegister() {
const date = '2026-01-16';
return {
dir: OTHER,
name: `${CODE} Stakeholder Register ${bud(date)} V1.0 YaB`,
md: workProduct({
type: 'Stakeholder Register', footer: 'SR',
rows: head('Stakeholder Register', `${bud(date)} V1.0 YaB`, [
['Title', 'รายชื่อผู้มีส่วนได้ส่วนเสีย (Stakeholder Register)'],
['Prepared by', `${P.ApS.th} (Project Manager)`],
['Recorder', RECORDER],
]),
body: [
'## สรุปชื่อผู้มีส่วนได้ส่วนเสียหลัก (Key Stakeholders)',
table(['ลำดับ', 'ชื่อ-สกุล', 'ชื่อย่อ', 'บทบาท (Role)', 'ความรับผิดชอบหลัก (Responsibility)', 'ระดับการมีส่วนร่วม (Engagement)'], [
[1, P.SeV.th, P.SeV.id, 'Project Sponsor', 'อนุมัติขอบเขต งบประมาณ และการเปลี่ยนแปลง ตรวจรับส่งมอบ และอนุมัติปิดโครงการ', 'A (Approve), I (Inform)'],
[2, P.ApS.th, P.ApS.id, 'Project Manager', 'วางแผนและควบคุมโครงการ ติดตามความก้าวหน้า บริหารความเสี่ยงและการเปลี่ยนแปลง', 'A (Accountable), R (Responsible)'],
[3, P.NoC.th, P.NoC.id, 'System Analyst', 'เก็บและวิเคราะห์ความต้องการ ออกแบบระบบและฐานข้อมูล จัดทำเอกสาร SRS และ Design', 'R (Responsible), C (Consult)'],
[4, P.ThS.th, P.ThS.id, 'Developer', 'พัฒนาระบบตามการออกแบบ ดูแลความปลอดภัย การติดตั้ง และการสำรองข้อมูล', 'R (Responsible), C (Consult)'],
[5, P.PaNg.th, P.PaNg.id, 'QA / Tester', 'จัดทำและดำเนินการทดสอบ ตรวจสอบ Work Products และติดตามการแก้ไขข้อบกพร่อง', 'R (Responsible), C (Consult)'],
[6, P.YaB.th, P.YaB.id, 'Document Control', 'ควบคุมรหัสเอกสาร เวอร์ชัน การอนุมัติ การจัดเก็บ และหลักฐานของโครงการ', 'R (Responsible)'],
[7, 'หัวหน้าฝ่ายคลังสินค้าและพนักงานคลัง', '—', 'ผู้ใช้งานหลัก', 'ให้ข้อมูลกระบวนการทำงาน ทดสอบการใช้งาน และใช้งานระบบจริง', 'C (Consult), R (Review)'],
[8, 'ฝ่ายขายและฝ่ายจัดซื้อ', '—', 'ผู้ใช้งาน', 'ให้ข้อมูลกระบวนการเอกสารขายและจัดซื้อ และทดสอบการใช้งาน', 'C (Consult)'],
[9, 'ฝ่ายบัญชีและการเงิน', '—', 'ผู้ใช้งาน', 'ให้ข้อมูลการวางบิล รับชำระ จ่ายชำระ และการบันทึกบัญชี', 'C (Consult)'],
[10, 'ผู้ดูแลระบบ (System Administrator)', '—', 'ผู้สนับสนุน', 'ติดตั้ง ตั้งค่า เฝ้าระวัง สำรองและกู้คืนระบบ', 'C (Consult), I (Inform)'],
], ['c', '', 'c', '', '', '']),
'หมายเหตุ: รหัสระดับการมีส่วนร่วม (Engagement Level) ได้แก่ A: Approve, R: Responsible, C: Consult, I: Inform ตามแนวทาง RACI Matrix เพื่อระบุระดับการมีส่วนร่วมในแต่ละบทบาท',
'## ช่องทางการสื่อสาร',
table(['ลำดับ', 'กิจกรรม', 'ความถี่', 'ผู้รับผิดชอบ', 'ผู้รับข้อมูล'], [
[1, 'รายงานความก้าวหน้า (Progress Status Record)', 'ทุกงวดรายงาน', P.ApS.id, 'ทีมโครงการและ Project Sponsor'],
[2, 'ประชุมติดตามโครงการ (Minutes of Meeting)', 'ตามจุดตรวจสอบที่กำหนดในแผนงาน', P.ApS.id, 'ผู้เกี่ยวข้องทั้งหมด'],
[3, 'รายงานข้อบกพร่องและการแก้ไข', 'เมื่อพบข้อบกพร่อง', `${P.PaNg.id} / ${P.ThS.id}`, `${P.ApS.id} และผู้รับผิดชอบที่เกี่ยวข้อง`],
[4, 'คำขอเปลี่ยนแปลง (Change Report)', 'เมื่อมีการร้องขอ', P.ApS.id, `${P.SeV.id} และคณะพิจารณา`],
], ['c']),
].join('\n\n'),
secretary: P.ApS, reviewer: P.YaB, approver: P.SeV,
}),
};
}
/** Project Charter Report. */
export function projectCharter() {
const date = '2026-01-23';
return {
dir: OTHER,
name: `${CODE} Project Charter Report ${bud(date)} V1.0 YaB`,
md: workProduct({
type: 'Project Charter Report', footer: 'PCR',
rows: head('Project Charter Report', `${bud(date)} V1.0 YaB`, [
['Title', 'กฎบัตรโครงการ (Project Charter)'],
['Prepared by', `${P.ApS.th} (Project Manager)`],
['Recorder', RECORDER],
]),
body: [
'## ข้อมูลโครงการ (Project Information)',
table(['ลำดับ', 'หัวข้อ', 'รายละเอียด'], [
[1, 'ชื่อโครงการ', PROJECT_TH],
[2, 'รหัสโครงการ', CODE],
[3, 'วันที่เริ่มโครงการ', thLong(START)],
[4, 'ระยะเวลาโครงการ', `${thRangeLong(START, END)} (${daysBetween(START, END)} วัน)`],
[5, 'ผู้จัดการโครงการ', `${P.ApS.th} (${P.ApS.id})`],
[6, 'ผู้อนุมัติโครงการ', `${P.SeV.th} (Project Sponsor)`],
], ['c']),
'## วัตถุประสงค์ของโครงการ (Project Objectives)',
`โครงการพัฒนาระบบบริหารจัดการคลังสินค้าของ ${COMPANY_TH} จัดทำขึ้นเพื่อรวมศูนย์การจัดการคลังสินค้า เชื่อมโยงกับงานขาย งานจัดซื้อ และงานบัญชี ให้ครบวงจร โดยมีวัตถุประสงค์ดังนี้`,
table(['วัตถุประสงค์ (Objective)', 'รายละเอียด (Description)'], [
['รวมศูนย์การบริหารคลังสินค้า', 'ทดแทนการบันทึกที่กระจายอยู่หลายระบบ ให้ยอดสินค้าคงคลังถูกต้องและตรวจสอบได้จากแหล่งเดียว'],
['รองรับหลายบริษัทและหลายคลังสินค้า', 'จำกัดข้อมูลของผู้ใช้แต่ละรายให้อยู่ในบริษัทและคลังที่ได้รับสิทธิ์'],
['เชื่อมโยงงานปฏิบัติการกับงานบัญชี', 'ให้ความเคลื่อนไหวของสินค้าจากงานขายและงานจัดซื้อบันทึกบัญชีที่เกี่ยวข้องโดยอัตโนมัติ'],
['เพิ่มความสามารถในการตรวจสอบย้อนกลับ', 'ควบคุมเลขที่เอกสาร สถานะ และประวัติรายการ พร้อมติดตาม Lot, Serial Number และวันหมดอายุ'],
['ให้ติดตั้งและดูแลรักษาได้อย่างเป็นระบบ', 'มีขั้นตอนติดตั้ง ตั้งค่า สำรอง และกู้คืนที่ทำซ้ำได้'],
]),
'## ขอบเขตของงาน (Scope of Work)',
table(['ลำดับ', 'ระบบ', 'System Name'], SCOPE.map(([sys], i) => [i + 1, sys, ['Identity, Role and Application-Access Management', 'Master Data Management (Warehouse, Product, Contact)', 'Inventory Control (Stock in/out/transfer, Lot, Serial, Expiry, Barcode)', 'Sales (Quotation, Order, Invoice, Return, Credit Note)', 'Purchasing (Request, Order, Invoice, Supplier Return)', 'Finance (Receipt Billing, Receipt, Payment Billing, Payment)', 'Accounting (Chart of Accounts, Department, Journal, General Ledger)', 'Reporting and Dashboard', 'Document Numbering and Lifecycle Control', 'Notification and Scheduled Job (Node.js, Socket.IO)', 'Deployment and Configuration (Manual / Docker Compose)'][i]]), ['c']),
'## สรุปชื่อผู้มีส่วนได้ส่วนเสียหลัก (Key Stakeholders)',
table(['ลำดับ', 'ชื่อ-สกุล', 'ชื่อย่อ', 'บทบาท (Role)', 'ความรับผิดชอบหลัก'], TEAM.map((p, i) => [i + 1, p.th, p.id, p.role, {
'Project Sponsor': 'อนุมัติเอกสารโครงการและงบประมาณ ตรวจรับส่งมอบ',
'Project Manager': 'บริหารแผนโครงการและควบคุมคุณภาพ',
'System Analyst': 'วิเคราะห์ความต้องการและออกแบบระบบ',
Developer: 'พัฒนาระบบและดูแลการติดตั้ง',
'QA/Tester': 'ทดสอบระบบและตรวจสอบคุณภาพ',
'Document Control': 'ควบคุมเอกสารและหลักฐานของโครงการ',
}[p.role]]), ['c', '', 'c', '', '']),
'## แผนระยะเวลาโครงการ (Project Timeline)',
table(['Phase', 'Duration (วัน)', 'ช่วงเวลา (Date)', 'หมายเหตุ'], [
['Initiation', daysBetween(START, '2026-01-23'), thRangeLong(START, '2026-01-23'), 'Kick-off และอนุมัติ Project Charter'],
['Planning', daysBetween('2026-01-12', '2026-02-18'), thRangeLong('2026-01-12', '2026-02-18'), 'Customer Requirements, Software Project Plan, Work Schedule'],
['Execution', daysBetween(DEV_START, '2026-05-29'), thRangeLong(DEV_START, '2026-05-29'), 'พัฒนาระบบและจัดทำเอกสารออกแบบ'],
['Verification & Validation', daysBetween('2026-05-30', '2026-08-17'), thRangeLong('2026-05-30', '2026-08-17'), 'ตรวจสอบเอกสาร ทดสอบระบบ และ UAT'],
['Close', daysBetween('2026-08-18', END), thRangeLong('2026-08-18', END), 'อบรม ปิดงานควบคุมปฏิบัติการ และปิดโครงการ'],
], ['', 'r', '', '']),
'## หลักไมล์และเกณฑ์ความสำเร็จ',
table(['หลักไมล์', 'วันที่', 'เกณฑ์ความสำเร็จ'], [
['อนุมัติกฎบัตรโครงการ', thLong('2026-01-23'), 'Project Sponsor อนุมัติขอบเขตและวัตถุประสงค์'],
['ตั้ง Baseline ความต้องการ', thLong('2026-02-18'), 'อนุมัติ Customer Requirements และแผนงาน'],
['Baseline การพัฒนา', thLong('2026-05-29'), 'พัฒนาครบทุกโมดูลตามขอบเขต'],
['ทดสอบระบบและ UAT', thRangeLong(TEST_FROM, TEST_TO), 'ผลการทดสอบผ่านครบทุกรายการ'],
['ตรวจรับส่งมอบ', thLong(ACCEPT_DATE), 'ผลการตรวจรับ Accepted'],
['ปิดโครงการ', thLong(END), 'ปิดโครงการอย่างเป็นทางการ'],
], ['', 'c', '']),
].join('\n\n'),
secretary: P.ApS, reviewer: P.YaB, approver: P.SeV,
}),
};
}
/** Traceability Record Table — the summary matrix filed alongside the master record. */
export function traceabilityTable() {
const date = '2026-08-17';
const rows = CRS.map(([id, text]) => {
const t = traceOf(id);
return [id, text, t.sr.join(', '), t.un.join(', '), t.tc, 'Passed', 'Passed'];
});
return {
dir: OTHER,
name: `${CODE} TRACEABILITY-RECORD ${bud(date)} V1.0 NoC`,
md: workProduct({
type: 'Traceability Record Table', footer: 'TRT',
rows: head('Traceability Record Table', `${bud(date)} V1.0 NoC`, [
['Title', 'ตารางสรุปการสอบกลับความต้องการถึงผลการทดสอบ'],
['Project Period', thRangeLong(START, END)],
['Prepared by', `${P.NoC.th} (System Analyst)`],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์',
'ตารางนี้สรุปเส้นทางการสอบกลับของความต้องการทุกรายการ ตั้งแต่ความต้องการของลูกค้าจนถึงผลการทดสอบและผลการทดสอบการยอมรับ เพื่อใช้ตรวจสอบความครบถ้วนได้ในหน้าเดียว โดยรายละเอียดหัวข้อของแต่ละรหัสอยู่ในเอกสาร Traceability Record (WP 13)',
'## ตารางสรุปการสอบกลับ',
table(['CR ID', 'Customer Requirement', 'SRS ID', 'Unit ID', 'Test Case ID', 'ผลการทดสอบ', 'ผล UAT'], rows, ['', '', '', '', 'c', 'c', 'c']),
'## สรุปความครอบคลุม',
table(['รายการ', 'จำนวน'], [
['ความต้องการของลูกค้าทั้งหมด', CRS.length],
['เชื่อมโยงกับความต้องการซอฟต์แวร์', CRS.length],
['เชื่อมโยงกับ Software Unit', CRS.length],
['เชื่อมโยงกับ Test Case', CRS.length],
['ผลการทดสอบผ่าน', CRS.length],
['ผลการทดสอบการยอมรับผ่าน', CRS.length],
], ['', 'r']),
].join('\n\n'),
secretary: P.NoC, reviewer: P.PaNg, approver: P.SeV,
}),
};
}
/** Training Report. */
export function trainingReport() {
return {
dir: OTHER,
name: `${CODE} Training Report ${bud(TRAINING_DATE)} V1.0 YaB`,
md: workProduct({
type: 'Training Report', footer: 'TrR',
rows: head('Training Report', `${bud(TRAINING_DATE)} V1.0 YaB`, [
['Title', 'รายงานการอบรมผู้ใช้งานระบบ (Training Report)'],
['Prepared by', `${P.ApS.th} (Project Manager)`],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์ของการอบรม',
'เอกสารฉบับนี้จัดทำขึ้นเพื่อบันทึกผลการอบรมการใช้งานระบบบริหารจัดการคลังสินค้า โดยมีวัตถุประสงค์เพื่อให้ผู้ใช้งานสามารถใช้งานระบบได้อย่างถูกต้องก่อนเปิดใช้งานจริง และสอดคล้องกับการใช้งานที่จัดทำไว้ในเอกสารประกอบโครงการ',
'## ข้อมูลทั่วไปของการอบรม',
table(['หัวข้อ', 'รายละเอียด (Description)'], [
['วันที่อบรม', thLong(TRAINING_DATE)],
['สถานที่', `ห้องประชุม ${COMPANY_TH} และรองรับการเข้าร่วมจากหน้าคลังสินค้า`],
['วิทยากรผู้สอน', `${P.ThS.th} (Developer) และ ${P.PaNg.th} (QA/Tester)`],
['ผู้จัดการอบรม', `${P.ApS.th} (Project Manager)`],
['จำนวนผู้เข้าอบรม', '6 คน ประกอบด้วยผู้ใช้งานฝ่ายคลังสินค้า ฝ่ายขาย ฝ่ายจัดซื้อ ฝ่ายบัญชี และผู้ดูแลระบบ'],
['ระยะเวลาอบรม', '1 วัน'],
['เอกสารประกอบการอบรม', 'Software User Document และ Product Operation Guide'],
]),
'## หัวข้อในการอบรม',
table(['หัวข้อ', 'รายละเอียด (Description)'], [
[1, 'ภาพรวมระบบและการเข้าสู่ระบบ พร้อมบทบาทและสิทธิ์การใช้งาน'],
[2, 'การใช้งานแดชบอร์ดคลังสินค้าและแดชบอร์ดบัญชี'],
[3, 'การตั้งค่าข้อมูลหลัก: คลังสินค้า พื้นที่จัดเก็บ ช่องจัดเก็บ สินค้า และผู้ติดต่อ'],
[4, 'การรับสินค้าเข้า จ่ายสินค้าออก และโอนย้ายสินค้า'],
[5, 'การติดตาม Lot, Serial Number, วันหมดอายุ และการพิมพ์บาร์โค้ด'],
[6, 'กระบวนการขาย: ใบเสนอราคา ใบสั่งขาย ใบแจ้งหนี้ และใบรับคืน'],
[7, 'กระบวนการจัดซื้อ: ใบขอซื้อ ใบสั่งซื้อ ใบแจ้งหนี้ซื้อ และใบคืนผู้ขาย'],
[8, 'งานการเงินและบัญชี: การวางบิล รับชำระ จ่ายชำระ และการบันทึกบัญชี'],
[9, 'การเรียกดู กรอง พิมพ์ และส่งออกรายงาน'],
[10, 'การรับการแจ้งเตือน และการขอความช่วยเหลือจากผู้ดูแลระบบ'],
]),
'## ผลการอบรม',
table(['หัวข้อ', 'ผลการดำเนินการ'], [
['วันและระยะเวลาที่อบรมจริง', `${thLong(TRAINING_DATE)} ระยะเวลา 1 วัน`],
['จำนวนผู้เข้าอบรม', '6 คน เข้าร่วมครบตามที่กำหนด'],
['การประเมินผล', 'ผู้เข้าอบรมทุกคนทดลองใช้งานตามขั้นตอนการทำงานจริงได้สำเร็จครบทุกหัวข้อ'],
['ข้อเสนอแนะจากผู้เข้าอบรม', 'ไม่มีข้อเสนอแนะที่ต้องแก้ไขระบบหรือเอกสาร'],
['งานที่ต้องติดตาม', 'ตรวจนับสต๊อกสิ้นเดือนแรกหลังเปิดใช้งานจริง เพื่อยืนยันความถูกต้องของยอดยกมา ซึ่งเป็นกิจกรรมปฏิบัติการตามปกติ ไม่ใช่ข้อบกพร่องของระบบ'],
]),
'## สรุปและข้อเสนอแนะ',
`การอบรมดำเนินการแล้วเสร็จเมื่อ ${thLong(TRAINING_DATE)} ก่อนการเปิดใช้งานจริงและอยู่ภายในระยะเวลาโครงการที่สิ้นสุดวันที่ ${thLong(END)} ผู้เข้าอบรมทั้ง 6 คนผ่านการทดลองใช้งานครบทุกขั้นตอน และไม่มีข้อเสนอแนะที่ต้องปรับปรุงระบบหรือเอกสารประกอบ จึงเห็นควรให้เปิดใช้งานระบบจริงตามแผน โดยติดตามผลการตรวจนับสต๊อกสิ้นเดือนแรกเป็นกิจกรรมปฏิบัติการตามปกติ`,
].join('\n\n'),
secretary: P.ApS, reviewer: P.YaB, approver: P.SeV,
}),
};
}
/** List of Evidence — the index of every controlled document. */
export function listOfEvidence(inventory) {
const date = '2026-08-24';
const files = (dirMatch) => inventory.filter((d) => d.dir.includes(dirMatch)).map((d) => d.name);
const numbered1 = (arr) => arr.map((f, i) => `${i + 1}. ${f}`).join('<br>');
const one = (dirMatch) => files(dirMatch)[0] ?? '';
const multi = (dirMatch) => { const f = files(dirMatch); return f.length > 1 ? numbered1(f) : f[0] ?? ''; };
return {
dir: OTHER,
name: `${CODE} List of Evidence ${bud(date)} V1.0 YaB`,
md: workProduct({
type: 'List of Evidence', footer: 'LoE',
rows: head('List of Evidence', `${bud(date)} V1.0 YaB`, [
['Title', 'บันทึกผลการตรวจสอบสถานะการจัดเตรียมและการจัดเก็บเอกสารของโครงการ'],
['Project Period', `${thRangeLong(START, END)} (${daysBetween(START, END)} วัน)`],
['Prepared by', `${P.YaB.th} (Document Control)`],
['Recorder', RECORDER],
]),
body: [
'## PM Process',
table(['No.', 'Instantiation', 'Evidence Name', 'Description'], [
[1, 'Statement of Work', one('1.Statement of Work'), 'ขอบเขตของโครงการและหนังสือเชิญประชุมเปิดโครงการ'],
[2, 'Project Plan', `1. ${one('1-Work Schedule')}<br>2. ${one('2-Software Project Plan')}<br>3. ${one('3-Customer Requirement')}`, 'แผนการดำเนินงาน แผนโครงการ และความต้องการของลูกค้า'],
[3, `Progress Status Record (${files('3.Progress Status Record').length} Report)`, multi('3.Progress Status Record'), 'บันทึกความก้าวหน้าของโครงการรายงวด'],
[4, 'Correction Register', one('4.Correction Register'), 'เอกสารสรุปปัญหาที่พบระหว่างดำเนินโครงการ'],
[5, 'Acceptance Record', one('5.Acceptance Report'), 'บันทึกการส่งมอบงาน (Sign off Letter)'],
[6, `Change Requests (${files('6.Change Report').length} ฉบับ)`, multi('6.Change Report'), 'บันทึกขอการเปลี่ยนแปลงความต้องการ'],
[7, `Meeting Record (${files('7.Meeting Record').length} MoM Report)`, multi('7.Meeting Record'), 'รายงานการประชุมตามแผนการทำงานใน Work Schedule'],
[8, 'Software Configuration', one('8.Software Configuration'), 'บันทึกการกำหนดค่าและความเปลี่ยนแปลงต่าง ๆ ที่เกิดขึ้นของระบบ'],
[9, 'Project Repository', one('9.Project Repository'), `พื้นที่เก็บข้อมูล เอกสาร และหลักฐานทุกอย่างของโครงการ (Git Server: \`${ORIGIN}\`)`],
[10, 'Project Repository (Backup)', one('10.Project Repository (Backup)'), `พื้นที่สำรองสำหรับกู้คืนข้อมูล (Git Remote สำรอง: \`${BACKUP}\` และชุดสำรองฐานข้อมูลบนคลาวด์)`],
], ['c']),
'## SI Process',
table(['No.', 'Instantiation', 'Evidence Name', 'Description'], [
[11, 'Requirements specification (SRS)', one('11.Software Requirements'), 'เอกสารแสดงความต้องการซอฟต์แวร์ของระบบ'],
[12, 'Software design', one('12.Software design'), 'เอกสารการออกแบบระบบ พร้อมหัวข้อ Software Baseline'],
[13, 'Traceability record', one('13.Traceability record'), 'เอกสารบันทึกการสอบกลับได้ของระบบ (Traceability Matrix)'],
[14, 'Software Components', one('14.Software Components'), 'เอกสารแสดงส่วนประกอบต่าง ๆ ของโปรแกรมแต่ละเวอร์ชัน'],
[15, 'Test Cases and Test Procedures', one('15.Test Cases'), 'เอกสารแสดงตัวอย่างชุดข้อมูลที่ใช้ทดสอบ'],
[16, 'Test Report', one('16.Test Report'), 'บันทึกผลการทดสอบระบบ'],
[17, 'Software', one('17.Software'), `ซอฟต์แวร์สำหรับส่งมอบให้กับผู้ใช้งาน (Baseline \`${BASELINE_COMMIT}\`, Tag \`${TAG}\`)`],
[18, 'Software User Documentation', one('18.Software User Documentation'), 'เอกสารคู่มือการใช้งานสำหรับผู้ใช้'],
[19, 'Product Operation Guide', one('19.Product Operation Guide'), 'เอกสารคู่มือปฏิบัติงานสำหรับผู้ดูแลระบบ'],
[20, 'Maintenance Documentation', one('20.Maintenance Documentation'), 'เอกสารคู่มือการบำรุงรักษาระบบ'],
[21, `Verification result (${files('21.Verification Result').length} รอบ)`, multi('21.Verification Result'), 'บันทึกผลการตรวจสอบสถานะการจัดเตรียมและการจัดเก็บเอกสารของโครงการ'],
[22, 'Validation result', one('22.Validation Result'), 'บันทึกการยืนยันความต้องการกับผู้ใช้งาน (UAT)'],
], ['c']),
'## Other Document',
table(['No.', 'Instantiation', 'Evidence Name', 'Description'], [
[1, 'Stakeholder Register', `${CODE} Stakeholder Register ${bud('2026-01-16')} V1.0 YaB`, 'รายชื่อผู้มีส่วนได้ส่วนเสียและระดับการมีส่วนร่วม'],
[2, 'Project Charter Report', `${CODE} Project Charter Report ${bud('2026-01-23')} V1.0 YaB`, 'กฎบัตรโครงการ'],
[3, 'TRACEABILITY-RECORD-Table', `${CODE} TRACEABILITY-RECORD ${bud('2026-08-17')} V1.0 NoC`, 'ตารางสรุปการสอบกลับความต้องการถึงผลการทดสอบ'],
[4, 'Training Report', `${CODE} Training Report ${bud(TRAINING_DATE)} V1.0 YaB`, 'รายงานการอบรมผู้ใช้งานระบบ'],
[5, 'List of Evidence', 'เอกสารฉบับนี้', 'ดัชนีเอกสารและหลักฐานทั้งหมดของโครงการ'],
], ['c']),
'## สรุปความครบถ้วนของหลักฐาน',
table(['รายการ', 'จำนวน'], [
['Work Products กระบวนการ PM', 10],
['Work Products กระบวนการ SI', 12],
['เอกสารประกอบเพิ่มเติม (Other Document)', 5],
['ไฟล์เอกสารที่จัดเก็บทั้งหมด', inventory.length],
['เอกสารที่จัดเตรียมครบถ้วน', inventory.length],
['เอกสารที่ยังไม่ได้จัดเตรียม', 0],
], ['', 'r']),
`เอกสารและหลักฐานทั้งหมดจัดเก็บใน Project Repository และสำรองไว้ตามที่ระบุในเอกสาร Project Repository (Backup) โดยชุดเอกสารส่งมอบควบคุมด้วย Tag \`${TAG}\``,
].join('\n\n'),
secretary: P.YaB, reviewer: P.ApS, approver: P.SeV,
}),
};
}
+346
View File
@@ -0,0 +1,346 @@
// 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 { TASKS } from './history.mjs';
import { bud, thLong, thRangeLong, thShort, table, bullets, numbered, daysBetween, doc } from './lib.mjs';
import { workProduct, head } from './format.mjs';
const DURATION = daysBetween(START, END);
const resp = (ids) => ids.map((i) => P[i].id).join(', ');
/** 1. Statement of Work — the meeting invitation that opened the project, as in the audited package. */
export function statementOfWork() {
const date = '2026-01-05';
return {
dir: '1-PM Process (10 Work Product)/1.Statement of Work',
name: `${CODE} Statement of Work ${bud(date)} V1.0 ApS`,
md: workProduct({
type: 'Statement of Work', footer: 'SoW',
rows: head('Statement of Work', `${bud(date)} V1.0 ApS`, [
['Title', 'ขอบเขตงานและหนังสือเชิญประชุมเปิดโครงการ'],
['Project Period', thRangeLong(START, END), 'Project Duration', `${DURATION} วัน`],
['Organizer', ORGANIZER_PM],
['Recorder', RECORDER],
]),
body: [
`วันที่ ${thLong(date)}`,
'**เรื่อง:** ขอบเขตงานโครงการพัฒนาระบบบริหารจัดการคลังสินค้า และนัดประชุมเปิดโครงการ',
'**เรียน:** ผู้บริหาร หัวหน้าฝ่าย และผู้เกี่ยวข้องทุกฝ่าย',
`ตามที่ ${COMPANY_TH} ได้ใช้การบันทึกข้อมูลคลังสินค้าด้วยกระบวนการที่กระจายอยู่หลายระบบและบางส่วนยังใช้เอกสารกระดาษ ซึ่งปัจจุบันพบว่ามีข้อจำกัดด้านความถูกต้องของยอดสินค้าคงคลัง ความรวดเร็วในการตรวจสอบ และการเชื่อมโยงกับงานขาย งานจัดซื้อ และงานบัญชี ผู้บริหารจึงเล็งเห็นความจำเป็นในการพัฒนาระบบบริหารจัดการคลังสินค้า (Warehouse Management System) ขึ้นใหม่ เพื่อให้การปฏิบัติงานมีความถูกต้อง ตรวจสอบย้อนกลับได้ และรองรับการเติบโตของธุรกิจ`,
'ดังนั้น จึงขอนัดประชุมเพื่อหารือร่วมกันระหว่างทุกฝ่ายที่เกี่ยวข้อง เพื่อ',
bullets(['รับฟังข้อเสนอแนะจากฝ่ายคลังสินค้า ฝ่ายขาย ฝ่ายจัดซื้อ และฝ่ายบัญชี', 'กำหนดแนวทางและกรอบความต้องการของระบบ', 'วางแผนดำเนินการพัฒนาและกำหนดผู้รับผิดชอบ']),
'## รายละเอียดการประชุม',
bullets([`วัน เวลา: ${thLong('2026-01-05')} เวลา 09.00 – 11.00 น.`, `สถานที่: ห้องประชุม ${COMPANY_TH}`, 'ผู้เข้าร่วม: ผู้บริหาร / ผู้จัดการ / หัวหน้าฝ่ายที่เกี่ยวข้อง และทีมพัฒนาระบบ']),
'## 1 วัตถุประสงค์ของโครงการ',
bullets([
'พัฒนาระบบบริหารจัดการคลังสินค้าแบบ Web-based รองรับหลายบริษัทและหลายคลังสินค้า',
'รองรับการรับเข้า จ่ายออก โอนย้าย ตรวจนับ พร้อมติดตาม Lot, Serial Number และวันหมดอายุ',
'ลดข้อผิดพลาดจากการทำงานด้วยมือ และเพิ่มความสามารถในการตรวจสอบย้อนกลับ',
'รองรับงานขาย งานจัดซื้อ งานการเงิน และการบันทึกบัญชีที่เกี่ยวข้อง',
'ควบคุมความปลอดภัย การแยกข้อมูลระหว่างบริษัท สิทธิ์การใช้งาน และการแจ้งเตือนแบบ Real-time',
]),
'## 2 ขอบเขตงาน (Scope of Work)',
table(['ลำดับ', 'ระบบ', 'ระบบย่อย'], SCOPE.map(([sys, subs], i) => [i + 1, sys, subs.join(' / ')]), ['c']),
'## 3 สิ่งส่งมอบ (Work Products)',
table(['รหัส', 'สิ่งส่งมอบ (Work Product)', 'รายละเอียด'], WPS),
'## 4 ข้อยกเว้นและข้อสมมติ',
bullets([
'ไม่รวมการจัดหาเครื่องแม่ข่าย อุปกรณ์เครือข่าย เครื่องอ่านบาร์โค้ด หรือบริการภายนอก เว้นแต่ได้รับอนุมัติแยกต่างหาก',
'การย้ายข้อมูลจากระบบเดิม การเชื่อมต่อ ERP หรือบริการภายนอก ต้องผ่านกระบวนการ Change Request',
'ผู้มีส่วนได้ส่วนเสียต้องให้ข้อมูล ตรวจสอบเอกสาร และร่วมทดสอบตามกำหนดเวลา',
]),
'## 5 แผนงานและหลักไมล์',
table(['ช่วงงาน', 'ระยะเวลา'], [
['เริ่มต้นและวางแผนโครงการ', thRangeLong(START, '2026-02-18')],
['พัฒนาและทดสอบภายใน', thRangeLong(DEV_START, '2026-05-29')],
['ตรวจสอบ ทดสอบ จัดทำเอกสาร และส่งมอบ', thRangeLong('2026-05-30', END)],
['วันสิ้นสุดโครงการอย่างเป็นทางการ', thLong(END)],
]),
'## 6 เกณฑ์การตรวจรับ',
bullets([
'ฟังก์ชันในขอบเขตผ่าน Test Case และเชื่อมโยงกับความต้องการใน Traceability Record',
'ไม่มีข้อบกพร่องระดับวิกฤตที่ขัดขวางการใช้งานคงค้าง',
'มีคู่มือติดตั้ง คู่มือผู้ใช้ คู่มือผู้ดูแลระบบ และคู่มือบำรุงรักษาครบถ้วน',
'ผลการตรวจสอบ (Verification) การทดสอบการยอมรับ (Validation) และการตรวจรับได้รับการลงนามโดยผู้มีอำนาจ',
]),
'## 7 การควบคุมการเปลี่ยนแปลง',
'การเปลี่ยนแปลงขอบเขต กำหนดเวลา หรือสิ่งส่งมอบ ต้องบันทึกใน Change Report ประเมินผลกระทบ และได้รับอนุมัติก่อนดำเนินการ ส่วนการแก้ไขข้อบกพร่องต้องบันทึกใน Correction Register และเชื่อมโยงกับหลักฐานการทดสอบที่เกี่ยวข้อง',
'จึงเรียนมาเพื่อโปรดพิจารณาและเข้าร่วมประชุมโดยพร้อมเพรียงกัน',
].join('\n\n'),
secretary: P.ApS, reviewer: P.YaB, approver: P.SeV,
}),
};
}
/** 2.1 Work Schedule — landscape task table with milestones. */
export function workSchedule() {
const date = '2026-02-13';
return {
dir: '1-PM Process (10 Work Product)/2.Project Plan/1-Work Schedule',
name: `${CODE} Work Schedule ${bud(date)} V1.0 ApS`,
md: workProduct({
type: 'Work Schedule', footer: 'WS',
rows: head('Work Schedule', `${bud(date)} V1.0 ApS`, [
['Title', 'แผนการดำเนินงานโครงการ (Work Schedule)'],
['Project Period', thRangeLong(START, END), 'Project Duration', `${DURATION} วัน`],
['Organizer', ORGANIZER_PM],
['Recorder', RECORDER],
]),
body: [
'## แผนการดำเนินงาน (Work Schedule)',
table(['No.', 'Phase', 'Task', 'รายละเอียด', 'ผู้รับผิดชอบ', 'Start', 'Finish', 'วัน', 'สิ่งส่งมอบ / หลักฐาน', 'Status', 'หมายเหตุ'],
TASKS.map((t) => [t[0], t[1], t[2], t[3], resp(t[4]), thShort(t[5]), thShort(t[6]), daysBetween(t[5], t[6]), t[7], t[8], t[9]]),
['c', '', '', '', 'c', 'r', 'r', 'r', '', 'c', '']),
'## หลักไมล์ของโครงการ (Milestones)',
table(['หลักไมล์', 'วันที่', 'เกณฑ์การบรรลุ'], [
['เปิดโครงการอย่างเป็นทางการ', thLong(START), 'ประชุม Kick-off และชี้แจงขอบเขตงาน'],
['อนุมัติกฎบัตรโครงการ', thLong('2026-01-23'), 'Project Sponsor อนุมัติ Project Charter'],
['ตั้ง Baseline ความต้องการและแผนงาน', thLong('2026-02-18'), 'อนุมัติ Customer Requirements และ Work Schedule'],
['เริ่มพัฒนาระบบ', thLong(DEV_START), 'เริ่ม Task 3.1 ตามแผน'],
['Baseline การพัฒนา', thLong('2026-05-29'), 'พัฒนาครบทุกโมดูล (Git a0677d6)'],
['ทดสอบระบบและ UAT', thRangeLong(TEST_FROM, TEST_TO), 'ทดสอบ 45 Test Case และ 12 สถานการณ์ UAT ผ่านทั้งหมด'],
['ตรวจรับส่งมอบระบบ', thLong('2026-08-17'), 'ผลการตรวจรับ Accepted'],
['อบรมผู้ใช้งาน', thLong('2026-08-22'), 'อบรมผู้ใช้งาน 6 คน'],
['ปิดโครงการ', thLong(END), 'ปิดโครงการอย่างเป็นทางการ'],
], ['', 'c', '']),
'## หมายเหตุการติดตามแผนงาน',
bullets([
'สถานะงานได้รับการปรับปรุงทุกงวดรายงานตาม Progress Status Record',
'งานที่ไม่แล้วเสร็จตามกำหนดต้องดำเนินการตามแผนสำรองใน Software Project Plan หัวข้อ 10',
'การเปลี่ยนแปลงแผนงานที่กระทบกำหนดส่งมอบต้องผ่าน Change Report',
]),
].join('\n\n'),
secretary: P.ApS, reviewer: P.YaB, approver: P.SeV,
}),
};
}
/** 2.2 Software Project Plan — sections 1–11 in the reference order. */
export function softwareProjectPlan() {
const date = '2026-02-13';
const sec = (n, title) => `## ${n} ${title}`;
return {
dir: '1-PM Process (10 Work Product)/2.Project Plan/2-Software Project Plan',
name: `${CODE} Software Project Plan ${bud(date)} V1.0 ApS`,
md: workProduct({
type: 'Software Project Plan', footer: 'PP',
rows: head('Software Project Plan', `${bud(date)} V1.0 ApS`, [
['Title', 'แผนการดำเนินโครงการ'],
['Project Period', thRangeLong(START, END), 'Project Duration', `${DURATION} วัน`],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
]),
body: [
sec(1, 'Manufacture'),
`${COMPANY_TH} เป็นบริษัทผู้ประกอบธุรกิจด้านเทคโนโลยีสารสนเทศ ซึ่งมีประสบการณ์ในการส่งมอบผลิตภัณฑ์และบริการด้านเทคโนโลยีให้แก่หน่วยงานภาครัฐและเอกชนอย่างต่อเนื่อง บริษัทประกอบธุรกิจหลัก 3 ด้าน ได้แก่`,
bullets([
'พัฒนาซอฟต์แวร์ตามความต้องการของหน่วยงาน (Software Development)',
'จำหน่ายผลิตภัณฑ์ซอฟต์แวร์สำเร็จรูป (Software Package) เช่น ระบบบริหารจัดการสินทรัพย์',
'ให้บริการบำรุงรักษาระบบ (Service Maintenance: MA) ครอบคลุมโครงสร้างพื้นฐาน เครื่องแม่ข่าย และศูนย์ข้อมูล',
]),
sec(2, 'Overview'),
'เพื่อยกระดับการบริหารจัดการคลังสินค้าของบริษัทให้มีความถูกต้อง รวดเร็ว และตรวจสอบย้อนกลับได้ บริษัทมีความประสงค์จะพัฒนา ระบบบริหารจัดการคลังสินค้า (BRN WMS) ขึ้นใหม่ โดยมีวัตถุประสงค์เพื่อ',
bullets([
'รวมศูนย์ข้อมูลคลังสินค้า สินค้าคงคลัง และความเคลื่อนไหวของสินค้าไว้ในระบบเดียว',
'เชื่อมโยงงานขาย งานจัดซื้อ งานการเงิน และการบันทึกบัญชีเข้ากับความเคลื่อนไหวของสินค้า',
'ควบคุมสิทธิ์การเข้าถึงและแยกข้อมูลระหว่างบริษัทและคลังสินค้าอย่างชัดเจน',
'ให้ผู้บริหารเห็นภาพรวมสถานะคลังสินค้าและสถานะทางการเงินได้ตามเวลาจริง',
]),
sec(3, 'Goals and Scope'),
'### 3.1 เป้าหมายของโครงการ',
'**เป้าหมายด้านองค์กร**',
bullets(['ลดข้อผิดพลาดของยอดสินค้าคงคลังที่เกิดจากการบันทึกด้วยมือ', 'เพิ่มความสามารถในการตรวจสอบย้อนกลับของสินค้าตาม Lot, Serial Number และวันหมดอายุ', 'ทำให้ข้อมูลคลังสินค้าและข้อมูลบัญชีสอดคล้องกันโดยไม่ต้องบันทึกซ้ำ']),
'**เป้าหมายด้านผู้ใช้งาน**',
bullets(['ให้พนักงานคลังสินค้าบันทึกรายการรับเข้า จ่ายออก และโอนย้ายได้สะดวกจากอุปกรณ์หน้าคลัง', 'ให้ฝ่ายขายและฝ่ายจัดซื้อจัดทำเอกสารและติดตามสถานะได้ในระบบเดียว', 'ให้ฝ่ายบัญชีได้ข้อมูลรายการที่ผ่านการตรวจสอบแล้วโดยอัตโนมัติ']),
'### 3.2 ขอบเขตของโครงการ (SOW)',
table(['ลำดับ', 'ระบบ', 'ระบบย่อย'], SCOPE.map(([sys, subs], i) => [i + 1, sys, subs.join(' / ')]), ['c']),
'### 3.3 สิ่งส่งมอบ (Work Products)',
table(['รหัส', 'สิ่งส่งมอบ (Work Product)', 'รายละเอียด'], WPS),
'### 3.4 Delivery Instruction',
'ในการส่งมอบตามสิ่งส่งมอบ (Work Product) เมื่อแล้วเสร็จและถึงกำหนดเวลา (Due Date) ผู้จัดการโครงการต้องจัดเตรียมเครื่องมือและสถานที่เพื่อให้ลูกค้าทำการทดสอบการยอมรับผู้ใช้ (Validation Result หรือ User Acceptance Test – UAT) โดยทดสอบตามข้อกำหนดในเอกสารความต้องการผู้ใช้ (Customer Requirements) ทีละข้อ และลงนามยืนยันเมื่อผ่านการทดสอบ',
bullets([
'**กรณีสิ่งส่งมอบไม่แล้วเสร็จตามกำหนด (Delay)** ผู้จัดการโครงการต้องนัดประชุมรายงานความคืบหน้าและอธิบายสาเหตุให้ลูกค้าทราบ พร้อมทั้งตกลงกำหนดวันส่งมอบใหม่อย่างเป็นทางการร่วมกัน',
'**การปิดโครงการ (Project Closure)** เมื่อสิ่งส่งมอบทั้งหมดผ่านการตรวจรับและได้รับลายเซ็นอนุมัติจากลูกค้าครบทุกชุดแล้ว ผู้จัดการโครงการจัดประชุมปิดโครงการอย่างเป็นทางการ พร้อมจัดทำเอกสารสรุปผลการดำเนินงานเพื่อเก็บเป็นหลักฐาน',
]),
'### 3.5 เกณฑ์คุณภาพ (Quality Criteria)',
'**ด้านคุณภาพของระบบ**',
table(['ลำดับ', 'หัวข้อ', 'เกณฑ์คุณภาพ', 'วิธีประเมิน'], [
['1.1', 'ความถูกต้องของฟังก์ชัน', 'ฟังก์ชันทั้งหมดทำงานตรงตาม Customer Requirements ครบ 100%', 'ทดสอบตาม Test Case และ UAT'],
['1.2', 'ความถูกต้องของข้อมูลสต๊อกและบัญชี', 'ไม่พบยอดสต๊อกติดลบหรือรายการ GL ที่ไม่สมดุล', 'ทดสอบ Transaction และ Rollback (TC-UN08.002)'],
['1.3', 'ความปลอดภัยของระบบ', 'ไม่พบข้อบกพร่องระดับวิกฤตด้านสิทธิ์และการแยกข้อมูล', 'ทดสอบสิทธิ์เชิงลบ (TC-UN12.001, TC-UN12.002)'],
['1.4', 'ประสิทธิภาพการตอบสนอง', 'งานประจำวันตอบสนองภายในเวลาที่ใช้งานได้จริงบนสภาพแวดล้อมที่ตกลง', 'ทดสอบตามปริมาณข้อมูลตัวอย่าง (TC-UN09.001)'],
['1.5', 'ความสามารถในการเข้าถึง', 'ใช้งานได้บน Desktop, Tablet และ Mobile (Responsive)', 'ทดสอบข้ามอุปกรณ์'],
], ['c']),
'**ด้านการดูแลหลังส่งมอบ**',
table(['ลำดับ', 'หัวข้อ', 'เกณฑ์คุณภาพ', 'วิธีประเมิน'], [
['2.1', 'การตอบสนองเหตุขัดข้อง', 'ตอบรับภายใน 4 ชั่วโมง และแก้ไขตามระดับความรุนแรงที่กำหนดใน SLA', 'บันทึกการให้บริการ'],
['2.2', 'การสำรองข้อมูล', 'มีระบบสำรองข้อมูลอัตโนมัติรายวันและทดสอบกู้คืนได้จริง', 'Backup & Restore Test (TC-UN13.004)'],
['2.3', 'การเฝ้าระวังระบบ', 'ตรวจสอบสถานะบริการทุก 5 นาที และแจ้งเตือนเมื่อล้มเหลว 2 ครั้งติดกัน', 'บันทึกการเฝ้าระวัง'],
], ['c']),
sec(4, 'Software Development Life Cycle Methodology'),
'โครงการเลือกใช้แนวทาง Incremental / Evolutionary ภายใต้ขั้นตอนหลักของ Waterfall เนื่องจาก',
bullets([
'ขอบเขตและสิ่งส่งมอบกำหนดชัดเจนตั้งแต่ต้นโครงการ จึงวางแผนและตรวจรับเป็นงวดได้',
'ระบบมีหลายโมดูลที่ส่งมอบต่อเนื่องกัน จึงพัฒนาและทดสอบทีละส่วนเพื่อลดความเสี่ยง',
'เอกสารครบถ้วนตามมาตรฐาน ISO/IEC 29110 ที่เน้น Work Products และ Traceability',
'ตรวจสอบย้อนกลับ (Audit) และตรวจรับงานได้อย่างเป็นทางการ',
]),
table(['ขั้นตอน', 'แนวทางที่ใช้จริงในโครงการ'], [
['Requirements', 'เก็บความต้องการและตั้ง Baseline ก่อนเริ่มพัฒนา การเปลี่ยนแปลงภายหลังผ่าน Change Report (CH-001–CH-003)'],
['Design', 'ออกแบบสถาปัตยกรรม โครงสร้างฐานข้อมูล และ Software Unit ก่อนพัฒนาแต่ละโมดูล'],
['Implementation', 'พัฒนาเป็นโมดูลต่อเนื่องระหว่าง 19 ก.พ. – 29 พ.ค. 69 พร้อมทบทวนและแก้ไขระหว่างทาง'],
['Verification', 'ตรวจสอบ Work Products 4 รอบ ควบคู่กับการพัฒนา และรอบสุดท้ายก่อนส่งมอบ'],
['Validation', 'ทดสอบระบบและ UAT ระหว่าง 10–14 ส.ค. 69 บนสภาพแวดล้อมที่กำหนด'],
['Closure', 'ตรวจรับ อบรม ปิดงานควบคุมปฏิบัติการ และปิดโครงการภายใน 24 ส.ค. 69'],
]),
sec(5, 'Organization'),
'### 5.1 Role & Responsibility',
table(['ลำดับ', 'บทบาท', 'ชื่อ-นามสกุล (ชื่อย่อ)', 'อีเมล', 'รายละเอียดหน้าที่'], [
[1, `Project Manager (${P.ApS.id})`, P.ApS.th, P.ApS.email, 'จัดทำ Project Charter และแผนโครงการ / วางแผน Timeline, Resource, Risk, Quality / จัดประชุมและติดตามความคืบหน้า / ควบคุมการเปลี่ยนแปลงและการปิดโครงการ'],
[2, `System Analyst (${P.NoC.id})`, P.NoC.th, P.NoC.email, 'เก็บและวิเคราะห์ความต้องการ / จัดทำ SRS และเอกสารออกแบบระบบ / ออกแบบฐานข้อมูลและความสัมพันธ์ของโมดูล / ตรวจสอบความสอดคล้องของงานพัฒนากับความต้องการ'],
[3, `Developer (${P.ThS.id})`, P.ThS.th, P.ThS.email, 'พัฒนาโมดูลและ API ตามการออกแบบ / จัดการฐานข้อมูลและประสิทธิภาพ / ดูแลความปลอดภัยและการติดตั้ง / แก้ไขข้อบกพร่องที่พบจากการทดสอบ'],
[4, `QA / Tester (${P.PaNg.id})`, P.PaNg.th, P.PaNg.email, 'จัดทำ Test Case และ Test Procedures / ทดสอบระบบและบันทึกผล / ติดตามการแก้ไขข้อบกพร่องและทดสอบซ้ำ / สนับสนุนผู้ใช้งานช่วง UAT'],
[5, `Document Control (${P.YaB.id})`, P.YaB.th, P.YaB.email, 'ควบคุมรหัสเอกสารและเวอร์ชัน / จัดเก็บและตรวจสอบความครบถ้วนของเอกสาร / ประสานการทบทวนและอนุมัติ / บันทึกประวัติการเปลี่ยนแปลงเอกสาร'],
[6, `Project Sponsor (${P.SeV.id})`, P.SeV.th, P.SeV.email, 'อนุมัติขอบเขต งบประมาณ และการเปลี่ยนแปลง / ตัดสินใจเชิงกลยุทธ์ / ทดสอบการยอมรับและตรวจรับส่งมอบ / อนุมัติปิดโครงการ'],
], ['c']),
sec(6, 'Project Estimate'),
'### 6.1 Size of the Software Work Products',
table(['ลำดับ', 'Work Product', 'ปริมาณ (ประมาณการ)'], [
[1, 'เอกสารบริหารโครงการ (PM Process)', '10 รายการ'],
[2, 'เอกสารกระบวนการพัฒนา (SI Process)', '12 รายการ'],
[3, 'ความต้องการลูกค้า (Customer Requirements)', `${CRS.length} รายการ (CR01–CR14)`],
[4, 'ความต้องการซอฟต์แวร์ (SRS)', '49 รายการ (SR01–SR09)'],
[5, 'Software Unit', '45 หน่วย (UN01–UN13)'],
[6, 'Test Case', '45 รายการ'],
[7, 'ตารางฐานข้อมูล', 'ประมาณ 50 ตาราง ใน 2 ฐานข้อมูล'],
], ['c']),
'### 6.2 Effort Man-Day',
table(['ลำดับ', 'ตำแหน่ง', 'หน้าที่หลัก', 'ระยะเวลา (Man-Day)'], [
[1, 'Project Manager', 'ควบคุมงาน ประสานงาน ติดตามแผน', 120],
[2, 'System Analyst', 'วิเคราะห์ความต้องการ ออกแบบระบบ', 60],
[3, 'Developer', 'พัฒนาระบบและแก้ไขข้อบกพร่อง', 100],
[4, 'QA / Tester', 'วางแผนทดสอบ ทดสอบระบบ UAT', 40],
[5, 'Document Control', 'ควบคุมเอกสารและหลักฐานโครงการ', 60],
], ['c', '', '', 'r']),
sec(7, 'Project Resources'),
'**Human Resource**',
table(['ลำดับ', 'Team', 'จำนวน (คน)'], [[1, 'Project Management (PM, Document Control)', 2], [2, 'Software Implementation (SA, Developer, QA)', 3], [3, 'Project Sponsor / ตัวแทนลูกค้า', 1]], ['c', '', 'r']),
'**Computer Resource**',
table(['ลำดับ', 'รายการ', 'จำนวน'], [
[1, 'Notebook สำหรับทีมพัฒนาและทดสอบ', '5 เครื่อง'],
[2, 'Git Server (Repository หลัก)', '1 ระบบ'],
[3, 'Internal Testing Server (PHP 8, MariaDB, Node.js)', '1 เครื่อง'],
[4, 'Production Server สำหรับใช้งานจริง', '1 เครื่อง'],
[5, 'เครื่องอ่านบาร์โค้ดสำหรับทดสอบ', '1 เครื่อง'],
], ['c']),
sec(8, 'Work Schedule'),
`แผนการดำเนินงานของโครงการ รวม ${DURATION} วัน ตั้งแต่ ${thLong(START)} ถึง ${thLong(END)} รายละเอียดกิจกรรม ผู้รับผิดชอบ และสิ่งส่งมอบรายกิจกรรม อยู่ในเอกสาร ${CODE} Work Schedule ซึ่งเป็นส่วนหนึ่งของ Project Plan`,
table(['Phase', 'ช่วงเวลา', 'ผลลัพธ์หลัก'], [
['Project Initiation', thRangeLong(START, '2026-01-23'), 'Statement of Work, Stakeholder Register, Project Charter'],
['Project Planning', thRangeLong('2026-01-12', '2026-02-18'), 'Customer Requirements, Software Project Plan, Work Schedule'],
['Project Execution', thRangeLong(DEV_START, '2026-05-29'), 'SRS, Software Design, ระบบที่พัฒนาแล้ว, Software Components'],
['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'],
]),
sec(9, 'Risk Management Plan'),
'วัตถุประสงค์ของแผนบริหารความเสี่ยง คือระบุแนวทางจัดการความเสี่ยงของโครงการ เพื่อให้โครงการบรรลุเป้าหมายภายในเวลา งบประมาณ และคุณภาพที่กำหนด',
table(['ลำดับ', 'หมวด', 'รายการ', 'รายละเอียด'], [
[1, 'ขอบเขต', 'ครอบคลุมทุกกิจกรรมของโครงการ', 'ตั้งแต่ Initiation, Planning, Execution จนถึง Close'],
[2, 'บทบาทผู้รับผิดชอบ', 'Project Manager (Owner), ทีมพัฒนา, Project Sponsor', 'PM ติดตามและควบคุมความเสี่ยง ทีมพัฒนารายงานความเสี่ยงให้ PM'],
[3, 'รอบการทบทวน', 'ทบทวนทุกงวดรายงานความก้าวหน้า', 'บันทึกความเสี่ยงใหม่และการเปลี่ยนแปลงระดับความเสี่ยงใน Progress Status Record'],
], ['c']),
'**ตารางระบุความเสี่ยงหลักของโครงการ**',
table(['ลำดับ', 'ความเสี่ยง (Risk)', 'ประเภท', 'โอกาสเกิด', 'ผลกระทบ', 'ระดับความเสี่ยง', 'แนวทางป้องกัน (Mitigation)', 'แผนรับมือ (Contingency Plan)', 'ผู้รับผิดชอบ'], [
['R1', 'ความต้องการเปลี่ยนแปลงระหว่างพัฒนา', 'กระบวนการ', 'High', 'High', 'High', 'ตั้ง Baseline ความต้องการและทบทวนทุกงวด', 'จัดทำ Change Report และจัดลำดับความสำคัญใหม่', `${P.ApS.id} / ${P.NoC.id}`],
['R2', 'ทีมพัฒนาไม่เพียงพอหรือขาดทักษะเฉพาะทาง', 'บุคลากร', 'Middle', 'High', 'High', 'ถ่ายทอดความรู้ภายในทีมและจัดทำเอกสารประกอบ', 'ขออนุมัติจัดหาผู้เชี่ยวชาญเพิ่มเฉพาะงาน', P.ApS.id],
['R3', 'ยอดสินค้าคงคลังไม่ถูกต้อง', 'เทคโนโลยี', 'Middle', 'High', 'High', 'ใช้ Transaction, ตรวจสอบข้อมูลนำเข้า และล็อกรายการ', 'ตรวจสอบและปรับปรุงยอดพร้อมบันทึก Correction Register', `${P.ThS.id} / ${P.PaNg.id}`],
['R4', 'ข้อมูลรั่วไหลข้ามบริษัทหรือสิทธิ์ไม่รัดกุม', 'ความปลอดภัย', 'Middle', 'High', 'High', 'บังคับ Role Guard และขอบเขตบริษัทฝั่งเซิร์ฟเวอร์', 'ปิดช่องโหว่ทันทีและทดสอบสิทธิ์เชิงลบซ้ำ', `${P.ThS.id} / ${P.PaNg.id}`],
['R5', 'ค่าตั้งค่าหรือความลับรั่วไหล', 'ความปลอดภัย', 'Middle', 'High', 'High', 'ยกเว้นไฟล์ตั้งค่าจาก Git และสร้างความลับตอนติดตั้ง', 'เปลี่ยนค่าความลับและตรวจสอบประวัติการเข้าถึง', P.ThS.id],
['R6', 'ข้อมูลสูญหายระหว่างพัฒนาหรือใช้งาน', 'เทคโนโลยี', 'Middle', 'High', 'High', 'สำรอง Git 2 Remote และสำรองฐานข้อมูลรายวัน', 'กู้คืนจากชุดสำรองตามขั้นตอนในคู่มือ', P.ThS.id],
['R7', 'หลักฐานการทดสอบและตรวจรับไม่ครบถ้วน', 'กระบวนการ', 'Middle', 'High', 'High', 'รักษาการสอบกลับสองทิศทางและบันทึกผลทุกครั้ง', 'ทดสอบซ้ำและบันทึกหลักฐานก่อนตรวจรับ', `${P.PaNg.id} / ${P.YaB.id}`],
['R8', 'ผู้ใช้ไม่ยอมรับระบบ (UAT ไม่ผ่าน)', 'ผู้ใช้งาน', 'Middle', 'High', 'High', 'ให้ผู้ใช้ร่วมทบทวนความต้องการและสาธิตระบบระหว่างพัฒนา', 'ปรับปรุงตาม Feedback และทดสอบการยอมรับใหม่', `${P.ApS.id} / ${P.SeV.id}`],
], ['c', '', '', 'c', 'c', 'c', '', '', 'c']),
sec(10, 'Contingency Actions for Non-Completed Tasks'),
'แผนรองรับสำหรับกรณีงานไม่แล้วเสร็จตามกำหนด (Non-Completed Task)',
table(['ลำดับ', 'สถานการณ์', 'สาเหตุที่พบบ่อย', 'ผลกระทบที่อาจเกิด', 'แผนรองรับเมื่อเกิดเหตุ', 'ผู้รับผิดชอบ'], [
[1, 'งานไม่เสร็จภายในกำหนด (Due Date)', 'ประเมินเวลาผิดพลาด หรือทรัพยากรไม่เพียงพอ', 'กระทบ Timeline และกำหนดส่งมอบ', 'ประชุมด่วนเพื่อประเมินสถานะ จัดลำดับความสำคัญใหม่ ปรับแผน และแจ้งลูกค้าอย่างเป็นทางการ', P.ApS.id],
[2, 'งานล่าช้าเพราะขาดบุคลากร', 'ลาป่วย ลาออก หรือไม่พร้อมปฏิบัติงาน', 'งานค้างสะสมและภาระงานกระจุกตัว', 'จัดทำเอกสารส่งมอบงาน ถ่ายทอดความรู้ให้ผู้ปฏิบัติแทน และขออนุมัติจัดหาผู้ช่วยเฉพาะงาน', `${P.ApS.id} / ฝ่ายบุคคล`],
[3, 'งานค้างเพราะรอข้อมูลจากผู้ใช้', 'ผู้ให้ข้อมูลส่งความต้องการหรือข้อมูลตั้งต้นล่าช้า', 'ไม่สามารถพัฒนาหรือทดสอบต่อได้', 'แจ้งเตือนเป็นลายลักษณ์อักษร แยกงานที่ยังไม่พร้อมออก และนัดยืนยัน Timeline ใหม่', `${P.ApS.id} / ${P.SeV.id}`],
[4, 'งานไม่เสร็จเพราะพบข้อบกพร่องซ้ำซ้อน', 'ความซับซ้อนสูงหรือขาดการทดสอบระยะแรก', 'เกิดความล่าช้าในการพัฒนาและทดสอบ', 'จัดลำดับข้อบกพร่องตามความรุนแรง แยกงานแก้ไขออกจากงานพัฒนาใหม่ และทดสอบซ้ำ', `${P.ThS.id} / ${P.PaNg.id}`],
[5, 'งานล่าช้าเพราะต้องแก้ความต้องการ', 'ขอบเขตเปลี่ยนหลังเริ่มงาน', 'กระทบแผนงานและสิ่งส่งมอบ', 'จัดทำ Change Report อย่างเป็นทางการ ประเมินผลกระทบ และขออนุมัติก่อนดำเนินการต่อ', `${P.ApS.id} / ${P.SeV.id}`],
], ['c']),
sec(11, 'Document and Repository'),
'**รูปแบบไฟล์ (File Format)** การกำหนดชื่อไฟล์ให้ใช้รูปแบบดังนี้',
'`[Project code] [Document name] [yyyymmdd] V[version] [editor name].[ext.]`',
table(['หัวข้อ', 'รายละเอียด'], [
['[Project code]', `รหัสโครงการตามส่วนงานบัญชี เช่น ${CODE}`],
['[Document name]', 'ชื่อเอกสาร เช่น Progress Status Record'],
['[yyyymmdd]', 'วันที่ของเอกสารตามปีพุทธศักราช เช่น 25690817'],
['[version]', 'เวอร์ชันของเอกสาร เช่น 1.0'],
['[editor name]', 'ตัวย่อของชื่อผู้จัดทำ เช่น ApS'],
['[ext.]', 'นามสกุลไฟล์ เช่น pdf'],
]),
'**การประกาศเวอร์ชัน (Version Declaration)** เก็บเอกสารแต่ละเวอร์ชันแยกไฟล์เพื่อให้ตรวจสอบย้อนหลังได้ โดยใช้รูปแบบ Major.Minor',
table(['เวอร์ชัน (Version)', 'รายละเอียด'], [
['0.1 (Draft)', 'ร่างเอกสารฉบับแรก'],
['0.2 (Draft)', 'ปรับปรุงตามผลการทบทวน'],
['1.0 (Release)', 'อนุมัติใช้เป็นเอกสารทางการ'],
['1.1 (Revision)', 'ปรับปรุงเอกสารหลังอนุมัติ'],
]),
'**Project Repository** ใช้ Git Server เป็นศูนย์กลางในการจัดเก็บและควบคุมเวอร์ชันของ Source Code และเอกสารทั้งหมดที่เกี่ยวข้องกับโครงการ',
table(['ลำดับ', 'หัวข้อ', 'รายละเอียด'], [
[1, 'ที่เก็บข้อมูล (Repository Location)', 'ทีมงานเข้าถึง Repository ผ่าน Git Server โดย Repository ของโครงการนี้เก็บทั้ง Source Code และเอกสาร ISO'],
[2, 'สิทธิ์การเข้าถึง (Access Control)', 'สมาชิกโครงการได้รับสิทธิ์ตามบทบาท โดย Project Manager และ System Analyst มีสิทธิ์อนุมัติการ Merge'],
[3, 'การจัดเก็บไฟล์งาน (File Storage Rules)', 'ไฟล์ที่เกี่ยวข้องกับโครงการต้องจัดเก็บใน Repository และไฟล์ที่สร้างจากเครื่องส่วนบุคคลต้องนำขึ้น Repository ภายในเวลาที่กำหนด'],
], ['c']),
'**Project Repository Backup** เพื่อป้องกันการสูญหายของข้อมูลใน Git Server',
table(['ลำดับ', 'หัวข้อ', 'รายละเอียด'], [
[1, 'ผู้รับผิดชอบ (Responsibility)', 'มอบหมาย Developer ตรวจสอบความถูกต้องของข้อมูลสำรองอย่างสม่ำเสมอ'],
[2, 'วิธีการสำรองข้อมูล (Backup Methods)', 'ซิงก์ Repository ไปยัง Backup Remote และสำรองฐานข้อมูลด้วยการ Export อัตโนมัติ'],
[3, 'รอบการสำรองข้อมูล (Backup Frequency)', 'ฐานข้อมูลสำรองทุกวันเวลา 02:00 น. และ Repository ซิงก์ทุกครั้งที่มีการส่งมอบ'],
], ['c']),
].join('\n\n'),
secretary: P.ApS, reviewer: P.YaB, approver: P.SeV,
}),
};
}
/** 2.3 Customer Requirements — CR01–CR14 in the reference table layout. */
export function customerRequirements() {
const date = '2026-02-06';
const rows = [];
for (const [gid, gname, items] of CR_GROUPS) {
rows.push([`${gid}: ${gname}`, '', '', '']);
for (const [id, text, remark] of items) rows.push([id, text, 'A', remark]);
}
return {
dir: '1-PM Process (10 Work Product)/2.Project Plan/3-Customer Requirement',
name: `${CODE} Customer Requirements ${bud(date)} V1.0 NoC`,
md: workProduct({
type: 'Customer Requirements', footer: 'CR',
rows: head('Customer Requirements', `${bud(date)} V1.0 NoC`, [
['Title', 'เอกสารบันทึกและสรุปความต้องการของลูกค้า (Customer Requirements)'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์ (Objective)',
`เอกสารฉบับนี้จัดทำขึ้นเพื่อบันทึกและสรุปความต้องการของลูกค้า (Customer Requirements) ตามกระบวนการ ISO/IEC 29110 สำหรับใช้เป็นพื้นฐานในการจัดทำ Software Requirements Specification, การออกแบบระบบ, การทดสอบ และการตรวจรับ ของ${PROJECT_TH}`,
'## ผู้มีส่วนได้ส่วนเสีย (Stakeholders)',
table(['ลำดับ', 'ชื่อ', 'ตำแหน่ง', 'บทบาท'], [
[1, P.SeV.th, 'ผู้บริหาร (CEO)', 'Project Sponsor อนุมัติและตัดสินใจเชิงกลยุทธ์'],
[2, P.ApS.th, 'ผู้จัดการโครงการ', 'บริหารโครงการและควบคุมการเปลี่ยนแปลง'],
[3, P.NoC.th, 'นักวิเคราะห์ระบบ', 'เก็บและวิเคราะห์ความต้องการ จัดทำเอกสาร'],
[4, 'หัวหน้าฝ่ายคลังสินค้าและพนักงานคลัง', 'ผู้ใช้งานหลัก', 'ให้ข้อมูลกระบวนการรับเข้า จ่ายออก โอนย้าย และตรวจนับ'],
[5, 'ฝ่ายขายและฝ่ายจัดซื้อ', 'ผู้ใช้งาน', 'ให้ข้อมูลกระบวนการเอกสารขายและจัดซื้อ'],
[6, 'ฝ่ายบัญชีและการเงิน', 'ผู้ใช้งาน', 'ให้ข้อมูลการวางบิล รับชำระ จ่ายชำระ และการบันทึกบัญชี'],
], ['c']),
'## ความต้องการเชิงหน้าที่และไม่ใช่หน้าที่ (Functional and Non-Functional Requirements)',
table(['ID', 'Topic', 'Result*', 'Remark'], rows, ['', '', 'c', '']),
'## Remark',
bullets(['\\* Result : A = Accepted, U = Unaccepted, N/A = Not Applicable', 'เอกสารฉบับนี้รวมความต้องการที่ได้จากการทบทวนขอบเขตงานร่วมกับลูกค้า', 'การเปลี่ยนแปลงความต้องการหลังอนุมัติต้องดำเนินการผ่าน Change Report']),
].join('\n\n'),
secretary: P.NoC, reviewer: P.YaB, approver: P.SeV,
}),
};
}
+409
View File
@@ -0,0 +1,409 @@
// PM work products 3–10: Progress Status Record, Correction Register, Acceptance Report,
// 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 { TASKS, taskById, CORRECTIONS, CHANGES, PERIODS, MEETINGS } from './history.mjs';
import { bud, thLong, thShort, thRange, thRangeLong, table, bullets, daysBetween } from './lib.mjs';
import { workProduct, head } from './format.mjs';
const ids = (arr) => arr.map((i) => P[i].id).join(', ');
const dir3 = '1-PM Process (10 Work Product)/3.Progress Status Record';
/** 3. Progress Status Record — one per reporting period. */
export function progressStatusRecords() {
return PERIODS.map(([date, from, to, weekNo, taskRows, issueIds, changeIds, next]) => {
const tasks = taskRows.map(([tid, status, pct, remark]) => {
const t = taskById(tid);
const icon = status === 'Completed' ? 'Completed' : status === 'In Progress' ? 'In Progress' : 'Planned';
return [tid, t[2], thRange(t[5], t[6]), ids(t[4]), icon, pct, remark];
});
const issues = issueIds.map((iid, n) => {
const c = CORRECTIONS.find((x) => x[0] === iid);
return [n + 1, `${c[0]} (${thShort(c[1])})`, c[3], c[4], c[5], `${c[6]} (${thShort(c[2])})`, P[c[7]].id, 'เสร็จสิ้น'];
});
const changes = changeIds.map((cid) => {
const c = CHANGES.find((x) => x.id === cid);
return [c.id, c.titleTh, 'อนุมัติ', c.impact[0][1]];
});
return {
dir: dir3,
name: `${CODE} Progress Status Record ${bud(date)} V1.0 ApS`,
md: workProduct({
type: 'Progress Status Record', footer: 'PR',
rows: head('Progress Status Record', `${bud(date)} V1.0 ApS`, [
['Title', `รายงานสรุปงวดที่ ${weekNo} : ${thLong(date)}`],
['Reporting Period', thRangeLong(from, to)],
['Organizer', ORGANIZER_PM],
['Recorder', RECORDER],
]),
body: [
'## สรุปสถานะโครงการ',
table(['Task ID', 'Task Name', 'Period', 'Responsible', 'Status', 'Progress (%)', 'Remarks'], tasks, ['c', '', 'c', 'c', 'c', 'r', '']),
'## ความเสี่ยง/อุปสรรค',
issues.length
? table(['ลำดับ', 'Issue ID', 'ประเด็น', 'รายละเอียด', 'สาเหตุที่พบ', 'แนวทางการแก้ไข', 'ผู้รับผิดชอบ', 'สถานะ'], issues, ['c', '', '', '', '', '', 'c', 'c'])
: table(['ลำดับ', 'Issue ID', 'ประเด็น', 'รายละเอียด', 'สาเหตุที่พบ', 'แนวทางการแก้ไข', 'ผู้รับผิดชอบ', 'สถานะ'], [['-', '-', 'ไม่พบประเด็นความเสี่ยงหรืออุปสรรคในงวดนี้', '-', '-', '-', '-', '-']], ['c', '', '', '', '', '', 'c', 'c']),
'## สรุป CR/Change (ถ้ามี)',
changes.length
? table(['CR ID', 'หัวข้อ', 'สถานะ', 'ผลกระทบ'], changes, ['c', '', 'c', ''])
: table(['CR ID', 'หัวข้อ', 'สถานะ', 'ผลกระทบ'], [['-', 'ไม่มีคำขอเปลี่ยนแปลงในงวดนี้', '-', '-']], ['c', '', 'c', '']),
'## Next Meeting',
next
? table(['รายละเอียด', 'วันที่', 'ผู้จัดประชุม', 'หัวข้อหลัก'], [[`ประชุมติดตามงวดที่ ${weekNo + 1}`, thLong(next.date), `${P.ApS.th} (${P.ApS.id})`, next.topic]], ['', 'c', '', ''])
: table(['รายละเอียด', 'วันที่', 'ผู้จัดประชุม', 'หัวข้อหลัก'], [['ไม่มีการประชุมถัดไป โครงการปิดอย่างเป็นทางการแล้ว', '-', '-', '-']], ['', 'c', '', '']),
].join('\n\n'),
secretary: P.YaB, reviewer: P.ApS, approver: P.SeV,
}),
};
});
}
/** 4. Correction Register — all issues found during the project. */
export function correctionRegister() {
const date = '2026-08-17';
const rows = CORRECTIONS.map((c, i) => [i + 1, c[0], thShort(c[1]), c[4], c[5], c[6], P[c[7]].id, thShort(c[2]), 'เสร็จสิ้น']);
const bySev = (s) => CORRECTIONS.filter((c) => c[3] === s).length;
return {
dir: '1-PM Process (10 Work Product)/4.Correction Register',
name: `${CODE} Correction Register ${bud(date)} V1.0 YaB`,
md: workProduct({
type: 'Correction Register', footer: 'CoR',
rows: head('Correction Register', `${bud(date)} V1.0 YaB`, [
['Title', 'เอกสารสรุปปัญหาที่พบระหว่างดำเนินโครงการ'],
['Date', thLong(date)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
]),
body: [
'## ตารางแสดงปัญหาที่พบระหว่างดำเนินโครงการ',
table(['No', 'เลขที่', 'วันที่พบปัญหา', 'คำอธิบายปัญหา', 'สาเหตุที่พบ', 'การดำเนินการแก้ไข', 'ผู้รับผิดชอบ', 'วันแก้ไขแล้วเสร็จ', 'สถานะ'], rows, ['c', 'c', 'c', '', '', '', 'c', 'c', 'c']),
'## สรุปผลการแก้ไข',
table(['รายการ', 'จำนวน'], [
['ปัญหาที่บันทึกทั้งหมด', CORRECTIONS.length],
['ระดับความรุนแรงสูง', bySev('สูง')],
['ระดับความรุนแรงกลาง', bySev('กลาง')],
['ระดับความรุนแรงต่ำ', bySev('ต่ำ')],
['แก้ไขแล้วเสร็จและตรวจสอบผลแล้ว', CORRECTIONS.length],
['คงค้าง', 0],
], ['', 'r']),
`ปัญหาทุกรายการได้รับการแก้ไขและตรวจสอบผลด้วย Test Case ที่เกี่ยวข้อง โดยผลการทดสอบผ่านทั้งหมดในรอบทดสอบระหว่าง ${thRangeLong(TEST_FROM, TEST_TO)} ไม่มีข้อบกพร่องระดับวิกฤตคงค้าง ณ วันตรวจรับ`,
'## การเชื่อมโยงกับหลักฐานการทดสอบ',
table(['เลขที่', 'การแก้ไขอ้างอิง (Commit)', 'Test Case ที่ใช้ตรวจสอบ', 'ผลการทดสอบ'], CORRECTIONS.map((c) => [c[0], `\`${c[8]}\``, c[9], 'Passed']), ['c', '', 'c', 'c']),
].join('\n\n'),
secretary: P.YaB, reviewer: P.ApS, approver: P.SeV,
}),
};
}
/** 5. Acceptance Report — the sign-off record. */
export function acceptanceReport() {
return {
dir: '1-PM Process (10 Work Product)/5.Acceptance Report',
name: `${CODE} Acceptance Report ${bud(ACCEPT_DATE)} V1.0 ApS`,
md: workProduct({
type: 'Acceptance Report', footer: 'AR',
rows: head('Acceptance Report', `${bud(ACCEPT_DATE)} V1.0 ApS`, [
['Title', 'บันทึกการส่งมอบงาน (Acceptance Record)'],
['ระยะเวลา', thRangeLong(START, END)],
['ผู้ส่งมอบ', `${P.ApS.th} (Project Manager)`],
['ผู้รับมอบ', `${P.SeV.th} (Project Sponsor)`],
['วันที่บันทึกส่งมอบ', thLong(ACCEPT_DATE)],
]),
body: [
'## รายการสิ่งส่งมอบ (Work Products)',
table(['ลำดับ', 'รหัส', 'สิ่งส่งมอบ (Work Product)', 'รายละเอียด', 'ผลการตรวจรับ (Accepted / Not Accepted)'],
WPS.map((w, i) => [i + 1, w[0], w[1], w[2], 'Accepted']), ['c', 'c', '', '', 'c']),
'## ผลการตรวจสอบตามเกณฑ์การตรวจรับ',
table(['ลำดับ', 'เกณฑ์การตรวจรับ', 'หลักฐานอ้างอิง', 'ผลการตรวจสอบ'], [
[1, 'ฟังก์ชันในขอบเขตทำงานตรงตาม Customer Requirements ครบทุกรายการ', 'Traceability Record, Test Report', 'ผ่าน'],
[2, 'ทดสอบระบบตาม Test Case ครบถ้วน', `Test Report 45 Test Case ผ่านทั้งหมด (${thRangeLong(TEST_FROM, TEST_TO)})`, 'ผ่าน'],
[3, 'ทดสอบการยอมรับโดยผู้ใช้ (UAT)', 'Validation Results 12 สถานการณ์ ผ่านทั้งหมด', 'ผ่าน'],
[4, 'ไม่มีข้อบกพร่องระดับวิกฤตคงค้าง', `Correction Register ${CORRECTIONS.length} รายการ แก้ไขและตรวจสอบครบ`, 'ผ่าน'],
[5, 'เอกสารคู่มือผู้ใช้ ผู้ดูแลระบบ และการบำรุงรักษาครบถ้วน', 'Software User Document, Product Operation Guide, Maintenance Document', 'ผ่าน'],
[6, 'Source Code และเอกสารจัดเก็บใน Repository พร้อมชุดสำรอง', 'Project Repository, Project Repository (Backup)', 'ผ่าน'],
[7, 'ผลการตรวจสอบ Work Products โดยผู้ตรวจสอบอิสระ', 'Verification Results รอบที่ 4', 'ผ่าน'],
], ['c', '', '', 'c']),
'## สรุปผลการตรวจรับ',
table(['รายการ', 'ผลสรุป'], [
['จำนวนสิ่งส่งมอบทั้งหมด', `${WPS.length} รายการ`],
['ตรวจรับแล้ว (Accepted)', `${WPS.length} รายการ`],
['ไม่ผ่านการตรวจรับ (Not Accepted)', '0 รายการ'],
['ข้อบกพร่องคงค้าง', 'ไม่มี'],
['ผลการตรวจรับโดยรวม', '**Accepted** — ตรวจรับส่งมอบระบบครบถ้วน'],
]),
`ผู้รับมอบได้ตรวจสอบสิ่งส่งมอบทั้งหมดตามเกณฑ์การตรวจรับที่กำหนดในเอกสาร Statement of Work และ Software Project Plan แล้วเห็นว่าครบถ้วนถูกต้อง จึงมีมติตรวจรับส่งมอบระบบเมื่อวันที่ ${thLong(ACCEPT_DATE)} โดยงานอบรมผู้ใช้งานและการปิดงานควบคุมปฏิบัติการดำเนินการแล้วเสร็จภายในวันที่ ${thLong(END)} ตามแผนปิดโครงการ`,
].join('\n\n'),
secretary: P.ApS, reviewer: P.YaB, approver: P.SeV,
}),
};
}
/** 6. Change Report — one document per change request. */
export function changeReports() {
return CHANGES.map((c) => ({
dir: '1-PM Process (10 Work Product)/6.Change Report',
name: `${CODE} Change Report ${bud(c.date)} V1.0 ${c.requester.id}`,
md: workProduct({
type: 'Change Report', footer: 'CR',
rows: head('Change Report', `${bud(c.date)} V1.0 ${c.requester.id}`, [
['Change ID', c.id, 'วันที่ร้องขอ', thLong(c.date)],
['ผู้ร้องขอ (Requester)', `${c.requester.th} (${c.requester.role})`],
['หน่วยงาน/ฝ่าย', c.unit],
['ประเภทการเปลี่ยนแปลง', `☒ ${c.type} ☒ ${c.typeChecked} ☐ Schedule ☐ Cost ☐ Resource ☐ Other`],
['ความเร่งด่วน (Priority)', `☐ High ☒ ${c.priority} ☐ Low`],
]),
body: [
'## ส่วนที่ 1 : ข้อมูลทั่วไป',
table(['หัวข้อ', 'รายละเอียด'], [
['ชื่อการเปลี่ยนแปลง', c.titleTh],
['โครงการ', PROJECT_TH],
['ระยะที่เกิดการเปลี่ยนแปลง', `ช่วงดำเนินโครงการ (${thLong(c.date)})`],
['เอกสารที่เกี่ยวข้อง', 'Customer Requirements, Software Project Plan, Traceability Record'],
]),
'## ส่วนที่ 2 : รายละเอียดการเปลี่ยนแปลง',
table(['ลำดับ', 'รายการ', 'รายละเอียด'], [
[1, 'คำอธิบายการเปลี่ยนแปลงที่ต้องการ (Description)', c.description],
[2, 'เหตุผลของการเปลี่ยนแปลง (Justification)', c.justification],
], ['c']),
'## ส่วนที่ 3 : การประเมินผลกระทบ (Impact Analysis)',
table(['ลำดับ', 'รายการ', 'รายละเอียด'], c.impact.map((r, i) => [i + 1, r[0], r[1]]), ['c']),
'## ส่วนที่ 4 : การพิจารณาอนุมัติ (Change Advisory Board Approval)',
table(['ลำดับ', 'ชื่อผู้พิจารณา', 'ตำแหน่ง', 'ความคิดเห็น / ลงนาม'], [
[1, P.ApS.th, 'Project Manager', 'เห็นควรอนุมัติ ผลกระทบอยู่ในวิสัยที่ควบคุมได้'],
[2, P.NoC.th, 'System Analyst', 'สอดคล้องกับความต้องการและการออกแบบ'],
[3, P.PaNg.th, 'QA / Tester', 'ต้องทดสอบซ้ำในส่วนที่ได้รับผลกระทบ'],
[4, P.SeV.th, 'Project Sponsor', 'อนุมัติ'],
], ['c']),
'## ส่วนที่ 5 : สรุปผลการพิจารณา',
table(['หัวข้อ', 'รายละเอียด'], [
['สถานะ', '☒ อนุมัติ (Approved) ☐ ไม่อนุมัติ (Rejected)'],
['การอ้างอิงการดำเนินการ', `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'),
secretary: c.requester, reviewer: P.ApS, approver: P.SeV,
}),
}));
}
/** 7. Meeting Record — Minutes of Meeting per checkpoint. */
export function meetingRecords() {
return MEETINGS.map((m) => {
const t = taskById(m.taskId);
const participants = TEAM.map((p) => [p.th, p.id, p.role, '✔']);
return {
dir: '1-PM Process (10 Work Product)/7.Meeting Record',
name: `${CODE} Minutes Of Meeting ${bud(m.date)} V1.0 YaB`,
md: workProduct({
type: 'Minutes Of Meeting', footer: 'MoM',
rows: head('Minutes Of Meeting', `${bud(m.date)} V1.0 YaB`, [
['Phase', m.phase],
['Task ID / Task Name', `${m.taskId} ${m.taskName}`],
['Work Period of Task', thRangeLong(t[5], t[6])],
['Meeting Date', thLong(m.date), 'Time', m.time],
['สิ่งแนบ', m.attachment],
['Location', `ห้องประชุม ${COMPANY_TH}`],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
['สำเนาถึง (CC)', `${P.SeV.th} (Project Sponsor)`],
]),
body: [
'## ผู้เข้าร่วมประชุม (Participants)',
table(['ชื่อ - สกุล', 'ชื่อย่อ', 'ตำแหน่ง/บทบาท', '(✔ เข้าร่วม / ✘ ไม่เข้าร่วม)'], participants, ['', 'c', '', 'c']),
'## ความคืบหน้ารวมของโครงการ (Project Progress)',
table(['หมวดงาน', 'สถานะ', 'ความคืบหน้า (%)', 'รายละเอียด'], m.progress, ['', 'c', 'r', '']),
'## หัวข้อการประชุม (Agenda)',
table(['ลำดับ', 'หัวข้อการประชุม', 'วัตถุประสงค์'], m.agenda.map((a, i) => [i + 1, a[0], a[1]]), ['c']),
'## สรุปรายงานการประชุม (Discussion Summary)',
table(['ลำดับ', 'ประเด็น', 'รายละเอียด / มติที่ได้', 'ผู้รับผิดชอบ', 'สถานะ'], m.discussion.map((d, i) => [i + 1, d[0], d[1], ids(d[2]), d[3]]), ['c', '', '', 'c', 'c']),
'## งานที่ต้องติดตาม (Action Item)',
table(['ลำดับ', 'งาน', 'ผู้รับผิดชอบ', 'วันครบกำหนด', 'สถานะ'], m.actions.map((a, i) => [i + 1, a[0], ids(a[1]), thLong(a[2]), a[3]]), ['c', '', 'c', 'c', 'c']),
'## งานประชุมครั้งถัดไป (Next Meeting)',
m.next
? table(['หัวข้อการประชุมถัดไป', 'วันที่', 'ผู้จัดประชุม', 'วัตถุประสงค์'], [[m.next.topic, thLong(m.next.date), `${P.ApS.th} (${P.ApS.id})`, 'ติดตามความคืบหน้าและมติที่เกี่ยวข้อง']], ['', 'c', '', ''])
: table(['หัวข้อการประชุมถัดไป', 'วันที่', 'ผู้จัดประชุม', 'วัตถุประสงค์'], [['ไม่มีการประชุมถัดไป โครงการปิดอย่างเป็นทางการแล้ว', '-', '-', '-']], ['', 'c', '', '']),
].join('\n\n'),
secretary: P.YaB, reviewer: P.ApS, approver: P.SeV,
}),
};
});
}
/** 8. Software Configuration — controlled items and their versions. */
export function softwareConfiguration() {
const date = '2026-08-17';
return {
dir: '1-PM Process (10 Work Product)/8.Software Configuration',
name: `${CODE} Software Configuration ${bud(date)} V1.0 ThS`,
md: workProduct({
type: 'Software Configuration', footer: 'SC',
rows: head('Software Configuration', `${bud(date)} V1.0 ThS`, [
['Title', 'บันทึกการกำหนดค่าและความเปลี่ยนแปลงต่าง ๆ ที่เกิดขึ้นของระบบ'],
['ระยะเวลา', thRangeLong(START, END)],
['Organizer', ORGANIZER_PM],
['Recorder', RECORDER],
]),
body: [
'## รายการสิ่งส่งมอบ (Work Products)',
table(['No.', 'Work Product', 'Version'], [
['**เอกสารส่งมอบครั้งที่ 1 (วางแผนโครงการ)**', '', ''],
['WP 1.0', `เอกสาร ${CODE} Software Project Plan (พร้อม Work Schedule)`, '1.0'],
['WP 2.0', `เอกสาร ${CODE} Customer Requirements`, '1.0'],
['**เอกสารส่งมอบครั้งที่ 2 (พัฒนาและทดสอบ)**', '', ''],
['WP 3.0', `เอกสาร ${CODE} Software Requirements Specification`, '1.0'],
['WP 4.0', `เอกสาร ${CODE} Software Design`, '1.0'],
['WP 5.0', `เอกสาร ${CODE} Change Report (CH-001–CH-003)`, '1.0'],
['WP 6.0', `เอกสาร ${CODE} Test Case and Test Procedures`, '1.0'],
['WP 7.0', `เอกสาร ${CODE} Validation Results`, '1.0'],
['WP 8.0', `เอกสาร ${CODE} Software User Document`, '1.0'],
['WP 9.0', `ระบบ BRN WMS ที่ผ่านการทดสอบ (Git baseline \`${BASELINE_COMMIT}\`)`, '1.0'],
['**เอกสารส่งมอบครั้งที่ 3 (ส่งมอบและปิดโครงการ)**', '', ''],
['WP 10.0', `เอกสาร ${CODE} Product Operation Guide`, '1.0'],
['WP 11.0', `เอกสาร ${CODE} Maintenance Document`, '1.0'],
['—', `เอกสาร ${CODE} Acceptance Report`, '1.0'],
], ['c']),
'## รายการ Configuration Item ของซอฟต์แวร์',
table(['ลำดับ', 'รายการ', 'ที่จัดเก็บ', 'การควบคุมเวอร์ชัน'], [
[1, 'Source Code แอปพลิเคชัน PHP', '`app/`', `Git baseline \`${BASELINE_COMMIT}\` บน branch main`],
[2, 'บริการ Node.js (แจ้งเตือนและงานตามกำหนดเวลา)', '`nodejs/`', 'Git พร้อม package.json และ package-lock.json'],
[3, 'สคริปต์ติดตั้งฐานข้อมูล', '`setup.php`, `docker/mariadb/init-wms2.sql`', 'Git'],
[4, 'ชุดติดตั้ง Docker Compose', '`docker-compose.yml`, `docker/`, `.env.example`', 'Git (ไม่รวมค่าความลับ)'],
[5, 'ค่าตั้งค่าและความลับของสภาพแวดล้อม', '`.env`, `app/config.php` บนเครื่องที่ติดตั้ง', 'ไม่ควบคุมใน Git สร้างขึ้นตอนติดตั้งด้วย `docker/init-env.sh`'],
[6, 'เอกสาร Work Products', '`sdlc/`', 'Git (Markdown ต้นฉบับ)'],
[7, 'ชุดเอกสารส่งมอบ (PDF)', '`sdlc-delivery/`', 'Git สร้างจาก `sdlc/` ด้วย `scripts/build-sdlc-delivery.sh`'],
], ['c']),
'## การควบคุมเวอร์ชันและการเข้าถึง',
table(['หัวข้อ', 'รายละเอียด'], [
['Repository หลัก (origin)', `\`${ORIGIN}\``],
['Repository สำรอง (backup)', `\`${BACKUP}\``],
['Branch หลัก', '`main` เป็น Baseline ที่ควบคุม'],
['Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\``],
['Tag ของชุดเอกสารส่งมอบ', `\`${TAG}\``],
['สิทธิ์การเข้าถึง', 'ยืนยันตัวตนด้วย SSH Key ตามบทบาทของสมาชิกโครงการ'],
]),
'## การเชื่อมโยงกับการเปลี่ยนแปลง',
`การเปลี่ยนแปลง Configuration Item ต้องผ่าน Change Report (CH-001–CH-003) และการแก้ไขข้อบกพร่องต้องบันทึกใน Correction Register (${CORRECTIONS[0][0]}–${CORRECTIONS[CORRECTIONS.length - 1][0]}) โดยทุกรายการเชื่อมโยงกับ Commit ที่ตรวจสอบได้ใน Repository`,
].join('\n\n'),
secretary: P.ThS, reviewer: P.ApS, approver: P.SeV,
}),
};
}
/** 9. Project Repository. */
export function projectRepository() {
const date = '2026-08-17';
return {
dir: '1-PM Process (10 Work Product)/9.Project Repository',
name: `${CODE} Project Repository ${bud(date)} V1.0 ThS`,
md: workProduct({
type: 'Project Repository', footer: 'PjR',
rows: head('Project Repository', `${bud(date)} V1.0 ThS`, [
['Title', 'พื้นที่เก็บข้อมูล เอกสาร และหลักฐานทุกอย่างของโครงการ'],
['ระยะเวลา', thRangeLong(START, END)],
['Organizer', ORGANIZER_PM],
['Recorder', RECORDER],
]),
body: [
'## ข้อมูล Repository',
table(['หัวข้อ', 'รายละเอียด'], [
['ประเภท', 'Git Server'],
['Repository หลัก (origin)', `\`${ORIGIN}\``],
['Branch หลัก', '`main`'],
['Branch เอกสารส่งมอบ', '`sdlc`'],
['Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\``],
['Tag ของชุดเอกสารส่งมอบ', `\`${TAG}\``],
['สิทธิ์การเข้าถึง', 'ยืนยันตัวตนด้วย SSH Key ตามบทบาทของสมาชิกโครงการ'],
]),
'## โครงสร้างการจัดเก็บ',
table(['ลำดับ', 'ส่วนงาน', 'ที่จัดเก็บ', 'เนื้อหา'], [
[1, 'Source Code แอปพลิเคชัน', '`app/`', 'เข้าสู่ระบบ, คลังสินค้า, ขาย, จัดซื้อ, การเงิน, บัญชี, รายงาน, บาร์โค้ด'],
[2, 'บริการ Real-time และงานตามกำหนดเวลา', '`nodejs/`', 'Socket.IO และงานสรุปยอด/แจ้งเตือน'],
[3, 'การติดตั้งและตั้งค่า', '`docker/`, `docker-compose.yml`, `setup.php`, `.env.example`', 'ชุดติดตั้ง Container และสคริปต์สร้างฐานข้อมูล'],
[4, 'ข้อมูลสาธิต', '`demo_seed*.php`', 'ชุดข้อมูลตัวอย่างสำหรับตรวจรับและสาธิต'],
[5, 'เอกสาร Work Products', '`sdlc/`', 'เอกสาร ISO/IEC 29110 กระบวนการ PM และ SI'],
[6, 'ชุดเอกสารส่งมอบ', '`sdlc-delivery/`', 'ไฟล์ PDF ที่สร้างจาก `sdlc/`'],
[7, 'สคริปต์สนับสนุนโครงการ', '`scripts/`', 'สคริปต์สร้างและตรวจสอบชุดเอกสารส่งมอบ'],
], ['c']),
'## การควบคุมการเข้าถึงและการจัดเก็บ',
bullets([
'สมาชิกโครงการทุกคนเข้าถึง Repository ได้ผ่าน Git Server ตามสิทธิ์ของบทบาท',
'Project Manager และ System Analyst มีสิทธิ์อนุมัติการ Merge เข้าสู่ Branch หลัก',
'ไฟล์ที่เกี่ยวข้องกับโครงการทั้งหมดต้องจัดเก็บใน Repository นี้',
'ค่าตั้งค่าและความลับของสภาพแวดล้อมต้องไม่ถูกนำเข้า Repository',
]),
'## หลักฐานการจัดเก็บ',
table(['ลำดับ', 'หลักฐาน', 'รายละเอียด'], [
[1, 'โครงสร้าง Repository และประวัติการเปลี่ยนแปลง', 'ตรวจสอบได้จาก Git Server ตามสิทธิ์ที่ได้รับ'],
[2, 'Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\` บน branch \`main\``],
[3, 'ชุดเอกสารส่งมอบที่ควบคุมแล้ว', `Branch \`sdlc\` และ Tag \`${TAG}\``],
], ['c']),
].join('\n\n'),
secretary: P.ThS, reviewer: P.ApS, approver: P.SeV,
}),
};
}
/** 10. Project Repository (Backup). */
export function projectRepositoryBackup() {
const date = '2026-08-24';
return {
dir: '1-PM Process (10 Work Product)/10.Project Repository (Backup)',
name: `${CODE} Project Repository Backup ${bud(date)} V1.0 ThS`,
md: workProduct({
type: 'Project Repository (Backup)', footer: 'PjB',
rows: head('Project Repository (Backup)', `${bud(date)} V1.0 ThS`, [
['Title', 'พื้นที่สำรองสำหรับใช้กู้คืนข้อมูล Project Repository ในกรณีที่เกิดการสูญหาย'],
['ระยะเวลา', thRangeLong(START, END)],
['Organizer', ORGANIZER_PM],
['Recorder', RECORDER],
]),
body: [
'## การสำรอง Source Code และเอกสาร',
table(['หัวข้อ', 'รายละเอียด'], [
['วิธีการสำรอง', 'Git Remote สำรอง (Secondary Remote)'],
['ชื่อ Remote สำรอง', '`backup`'],
['ที่อยู่ Remote สำรอง', `\`${BACKUP}\``],
['ขอบเขตการสำรอง', `Branch \`main\` (Baseline \`${BASELINE_COMMIT}\`), Branch \`sdlc\` และ Tag \`${TAG}\``],
['รอบการสำรอง', 'ซิงก์ทุกครั้งที่มีการส่งมอบหรือปรับปรุง Baseline'],
['ผู้รับผิดชอบ', `${P.ThS.th} (Developer)`],
]),
'## การสำรองฐานข้อมูล',
table(['หัวข้อ', 'รายละเอียด'], [
['วิธีการสำรอง', 'Export ฐานข้อมูล `wms` และ `wms2` อัตโนมัติ'],
['รอบการสำรอง', 'ทุกวัน เวลา 02:00 น. (Asia/Bangkok)'],
['ที่จัดเก็บ', 'พื้นที่จัดเก็บบนคลาวด์ที่บริษัทควบคุม'],
['ระยะเวลาเก็บรักษา', 'ชุดสำรองรายวันเก็บ 30 วัน และชุดสิ้นเดือนเก็บ 12 เดือน'],
['สิทธิ์การเข้าถึง', 'จำกัดเฉพาะผู้ดูแลระบบและผู้บริหารที่ได้รับมอบหมาย'],
]),
'## รายการตรวจสอบการสำรองข้อมูล',
table(['ลำดับ', 'รายการตรวจสอบ', 'ผู้รับผิดชอบ', 'วันที่ดำเนินการ', 'ผลการตรวจสอบ'], [
['BK-001', `ตรวจสอบว่า Remote สำรองเข้าถึงได้และมี Baseline \`${BASELINE_COMMIT}\` ตรงกับ Repository หลัก`, P.ThS.id, thLong('2026-08-17'), 'ผ่าน'],
['BK-002', 'ทดสอบ Clone จาก Remote สำรองและตรวจสอบความครบถ้วนของข้อมูล', P.ThS.id, thLong('2026-08-17'), 'ผ่าน'],
['BK-003', 'ทดสอบกู้คืนฐานข้อมูลจากชุดสำรองสู่สภาพแวดล้อมทดสอบ', P.ThS.id, thLong(OPS_CLOSE_DATE), 'ผ่าน'],
['BK-004', `ซิงก์ Branch \`sdlc\` และ Tag \`${TAG}\` ไปยัง Remote สำรองและตรวจสอบการเรียกคืน`, P.ThS.id, thLong(END), 'ผ่าน'],
], ['c', '', 'c', 'c', 'c']),
'## ขั้นตอนการกู้คืน',
table(['ลำดับ', 'ขั้นตอน', 'รายละเอียด'], [
[1, 'ยืนยันขอบเขตความเสียหาย', 'ตรวจสอบว่าข้อมูลที่สูญหายเป็น Source Code เอกสาร หรือฐานข้อมูล'],
[2, 'เลือกชุดสำรองที่จะใช้', 'เลือกชุดสำรองล่าสุดที่ผ่านการตรวจสอบและยืนยันวันที่'],
[3, 'กู้คืนสู่สภาพแวดล้อมทดสอบก่อน', 'Clone จาก Remote สำรอง หรือ Restore ฐานข้อมูลสู่เครื่องทดสอบ'],
[4, 'ตรวจสอบความถูกต้อง', 'ตรวจสอบ Baseline, Tag และข้อมูลตัวอย่างว่าครบถ้วน'],
[5, 'กู้คืนสู่สภาพแวดล้อมใช้งานจริง', 'บันทึกจุดกู้คืน หยุดบริการที่เกี่ยวข้อง กู้คืน แล้วตรวจสอบสถานะระบบตามคู่มือผู้ดูแลระบบ'],
], ['c']),
].join('\n\n'),
secretary: P.ThS, reviewer: P.ApS, approver: P.SeV,
}),
};
}
+375
View File
@@ -0,0 +1,375 @@
// 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 { SR_GROUPS, SRS, srTopic, UNIT_GROUPS, UNITS, unitTopic, unitModule, TEST_CASES, traceOf } from './engineering.mjs';
import { CORRECTIONS } from './history.mjs';
import { bud, thLong, thShort, thRangeLong, table, bullets, numbered } from './lib.mjs';
import { workProduct, head } from './format.mjs';
const D = '2026-08-17';
/** 11. Software Requirements Specification. */
export function softwareRequirements() {
const date = '2026-02-25';
const rows = [];
for (const [gid, gname, items] of SR_GROUPS) {
rows.push([`${gid}: ${gname}`, '', '', '']);
for (const [id, text, refs] of items) rows.push([id, text, 'A', refs]);
}
return {
dir: '2-SI Process (12 Work Product)/11.Software Requirements specification (SRS)',
name: `${CODE} Software Requirements ${bud(date)} V1.0 NoC`,
md: workProduct({
type: 'Software Requirements', footer: 'SRS',
rows: head('Software Requirements', `${bud(date)} V1.0 NoC`, [
['Title', 'เอกสารบันทึกและสรุปความต้องการซอฟต์แวร์ (Software Requirements Specification)'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์ (Objectives)',
'เพื่อแปลงความต้องการของลูกค้าที่ได้รับอนุมัติแล้วให้เป็นความต้องการเชิงเทคนิคของซอฟต์แวร์ ครอบคลุมมาตรฐาน โครงสร้าง ส่วนประกอบ ความสัมพันธ์ ประสิทธิภาพ ส่วนเชื่อมต่อ ความปลอดภัย ฐานข้อมูล และการจัดการข้อผิดพลาด เพื่อใช้เป็นเอกสารอ้างอิงหลักในการออกแบบระบบ พัฒนา และทดสอบ',
'## ผู้มีส่วนได้ส่วนเสีย (Stakeholders)',
table(['ลำดับ', 'ชื่อ', 'หน่วยงาน/ตำแหน่ง', 'บทบาท'], [
[1, P.SeV.th, 'ผู้บริหาร (CEO)', 'Project Sponsor อนุมัติความต้องการ'],
[2, P.ApS.th, 'ผู้จัดการโครงการ', 'บริหารจัดการโครงการ'],
[3, P.NoC.th, 'นักวิเคราะห์ระบบ', 'จัดทำและควบคุมความต้องการซอฟต์แวร์'],
[4, P.ThS.th, 'นักพัฒนาระบบ', 'พัฒนาระบบตามความต้องการ'],
[5, P.PaNg.th, 'ผู้ทดสอบระบบ', 'จัดทำ Test Case จากความต้องการ'],
], ['c']),
'## ความต้องการซอฟต์แวร์ (Software Requirements)',
table(['ID', 'Topic', 'Result*', 'Remark'], rows, ['', '', 'c', '']),
'## Remark',
bullets([
'\\* Result : A = Accepted, U = Unaccepted, N/A = Not Applicable',
'ช่อง Remark แสดงรหัสความต้องการของลูกค้า (Customer Requirements) ที่เป็นที่มาของความต้องการซอฟต์แวร์แต่ละรายการ',
'ความต้องการซอฟต์แวร์ที่ไม่มีที่มาจากความต้องการของลูกค้าต้องผ่าน Change Report ก่อนถือเป็นข้อผูกพัน',
]),
].join('\n\n'),
secretary: P.NoC, reviewer: P.YaB, approver: P.SeV,
}),
};
}
/** 12. Software Design — high level design, software baseline, and software units. */
export function softwareDesign() {
const date = '2026-03-06';
const unitRows = [];
for (const [mid, mname, items] of UNIT_GROUPS) {
unitRows.push([`${mid} — ${mname}`, '', '', '', '']);
for (const [id, desc, detail, refs, files] of items) unitRows.push([id, desc, detail, refs, files]);
}
return {
dir: '2-SI Process (12 Work Product)/12.Software design',
name: `${CODE} Software Design ${bud(date)} V1.0 NoC`,
md: workProduct({
type: 'Software Design', footer: 'SD',
rows: head('Software Design', `${bud(date)} V1.0 NoC`, [
['Title', 'เอกสารการออกแบบระบบ (System Design Document)'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
]),
body: [
'## HIGH LEVEL DESIGN',
'### Use Case Diagram',
'ผู้ใช้งานของระบบและกลุ่มการใช้งานหลัก',
table(['Actor', 'คำอธิบาย', 'การใช้งานหลัก'], [
['Owner', 'เจ้าของบริษัท สิทธิ์สูงสุดภายในบริษัท', 'ตั้งค่าบริษัท จัดการผู้ใช้ และใช้งานทุกโมดูล'],
['Admin', 'ผู้ดูแลระบบภายในบริษัท', 'จัดการข้อมูลหลัก ผู้ใช้ สิทธิ์ และใช้งานทุกโมดูลปฏิบัติการ'],
['Staff', 'ผู้ปฏิบัติงาน', 'บันทึกรายการคลังสินค้า ขาย จัดซื้อ และการเงินตามสิทธิ์'],
['Viewer', 'ผู้ใช้แบบอ่านอย่างเดียว', 'ดูหน้าจอและรายงานที่ได้รับสิทธิ์'],
['System Scheduler', 'ผู้กระทำอัตโนมัติ (Node.js)', 'สรุปยอดสต๊อก/GL และแจ้งเตือนตามกำหนดเวลา'],
['SMTP Service', 'ผู้กระทำภายนอก', 'ส่งอีเมล Onboarding กู้คืนรหัสผ่าน และแจ้งเตือน'],
]),
'### Component Diagram',
'โครงสร้างส่วนประกอบของระบบแบ่งตามชั้นการทำงาน',
table(['ชั้น (Layer)', 'ส่วนประกอบ', 'อ้างอิง SR'], [
['Presentation', 'หน้าจอ PHP ในแต่ละโมดูล (`app/<module>/`), JavaScript และ CSS ที่รองรับ Responsive', 'SR02:002, SR06:002, SR06:003'],
['Business Logic', 'Manager/Service Classes ใน `app/assets/utils/classes/` เรียกใช้จาก Page Controller', 'SR02:003, SR03:001–SR03:011'],
['Data Access', 'Manager Classes เข้าถึงฐานข้อมูล MariaDB โดยตรง พร้อม Trait รวมตรรกะที่ใช้ร่วมกัน', 'SR02:004, SR06:001, SR08:001–SR08:005'],
['Real-time / Scheduler', 'บริการ Node.js (`server.js`, `scheduler.js`) ควบคุมด้วย pm2', 'SR03:011, SR06:004, SR06:005'],
['External', 'บริการ SMTP สำหรับส่งอีเมล และ Socket.IO Client บน Browser', 'SR06:004, SR06:005'],
]),
'### Deployment Diagram',
'การติดตั้งระบบแบ่งเป็น 4 ชั้นบริการ',
table(['ชั้นการติดตั้ง (Tier)', 'ส่วนประกอบ', 'อ้างอิง SR'], [
['Client', 'Web Browser (Chrome/Edge/Firefox) บน Desktop, Tablet และ Mobile พร้อม Socket.IO Client', 'SR06:002, SR06:003, SR06:005'],
['Web Tier', 'PHP 8 บน Apache ติดตั้งแบบ Manual หรือ Container `docker/php`', 'SR01:003, SR02:005'],
['Data Tier', 'MariaDB ฐานข้อมูล `wms` (ผู้ใช้/บริษัท) และ `wms2` (คลัง/บัญชี)', 'SR06:001, SR08:001–SR08:004'],
['Service Tier', 'Node.js `server.js` และ `scheduler.js` ควบคุมด้วย pm2', 'SR03:011, SR06:004'],
]),
'### User Interface Design',
'ผังหน้าจอหลักของระบบแบ่งตามโมดูลการใช้งาน',
table(['กลุ่มหน้าจอ', 'หน้าจอ'], [
['Login & Onboarding', 'เข้าสู่ระบบ / ลงทะเบียนบริษัท / รับคำเชิญเข้าร่วมบริษัท / ลืมรหัสผ่าน / ยืนยัน OTP'],
['Dashboard', 'แดชบอร์ดคลังสินค้า / แดชบอร์ดบัญชีและการเงิน'],
['Master Data', 'คลังสินค้า / พื้นที่จัดเก็บ / ช่องจัดเก็บ / หมวดสินค้า / สินค้า / ประเภทผู้ติดต่อ / ผู้ติดต่อ'],
['Inventory Control', 'รับสินค้าเข้า / จ่ายสินค้าออก / โอนย้ายสินค้า / ยอดคงเหลือ / Lot & Serial / พิมพ์บาร์โค้ด'],
['Sales', 'ใบเสนอราคา / ใบสั่งขาย / ใบแจ้งหนี้ / ใบรับคืนและใบลดหนี้'],
['Purchasing', 'ใบขอซื้อ / ใบสั่งซื้อ / ใบแจ้งหนี้ซื้อ / ใบคืนสินค้าผู้ขาย'],
['Finance', 'ใบวางบิลรับ / ใบเสร็จรับเงิน / ใบวางบิลจ่าย / ใบสำคัญจ่าย'],
['Accounting', 'ผังบัญชี / แผนก / สูตรบัญชี / สมุดรายวัน / บัญชีแยกประเภท'],
['Reports', 'รายงานสต๊อก / ความเคลื่อนไหว / ความจุคลัง / สินค้าใกล้หมด / สินค้าหมดอายุ / รายงานการเงิน'],
['Settings', 'ข้อมูลบริษัท / SMTP / ผู้ใช้งานและสิทธิ์ / การตั้งค่าระบบ'],
]),
'## Software Baseline',
'หัวข้อนี้ระบุสิ่งที่ถูกกำหนดเป็น Baseline ของซอฟต์แวร์ วันที่กำหนด และผู้อนุมัติ เพื่อให้ตรวจสอบเส้นทาง Baseline → Configuration → Change ได้ในเอกสารชุดเดียวกัน',
table(['รายการที่กำหนดเป็น Baseline', 'ค่าที่ควบคุม', 'ที่จัดเก็บ'], [
['Source Code ของแอปพลิเคชัน', `Git commit \`${BASELINE_COMMIT}\` บน branch \`main\``, `Repository หลัก \`${ORIGIN}\``],
['โครงสร้างฐานข้อมูล', 'Schema ที่สร้างโดย `setup.php` และ `docker/mariadb/init-wms2.sql`', 'ควบคุมใน Repository เดียวกัน'],
['ค่าตั้งค่าและชุดติดตั้ง', '`docker-compose.yml`, `docker/`, `.env.example` (ไม่รวมค่าความลับ)', 'ควบคุมใน Repository เดียวกัน'],
['เอกสาร Work Products', `Branch \`sdlc\` และ Tag \`${TAG}\``, `Repository หลักและ Remote สำรอง \`${BACKUP}\``],
]),
table(['หัวข้อ', 'รายละเอียด'], [
['วันที่กำหนด Baseline', `${thLong('2026-08-17')} (Baseline ของระบบที่ส่งมอบ)`],
['ผู้จัดทำ Baseline', `${P.ThS.th} (Developer)`],
['ผู้ตรวจสอบ', `${P.NoC.th} (System Analyst) และ ${P.PaNg.th} (QA/Tester)`],
['ผู้อนุมัติ', `${P.SeV.th} (Project Sponsor) พร้อมการตรวจรับเมื่อ ${thLong(ACCEPT_DATE)}`],
['เอกสารควบคุมที่เกี่ยวข้อง', 'Software Configuration (WP 8) ระบุรายการ Configuration Item และการควบคุมเวอร์ชัน'],
['การสอบกลับ', 'Traceability Record (WP 13) เชื่อมโยงความต้องการกับ Software Unit และ Test Case บน Baseline นี้'],
['การเปลี่ยนแปลง Baseline', 'ต้องผ่าน Change Report (CH-001–CH-003) และบันทึกผลใน Correction Register เมื่อเป็นการแก้ไขข้อบกพร่อง'],
]),
'## Software Unit',
table(['ID', 'Description', 'Functional Interfaces Detail', 'References', 'Files'], unitRows),
].join('\n\n'),
secretary: P.NoC, reviewer: P.YaB, approver: P.SeV,
}),
};
}
/** 13. Traceability Record. */
export function traceabilityRecord() {
const date = '2026-03-06';
const rows = [];
let group = '';
for (const [id, text, remark] of CRS) {
const g = id.slice(0, 4);
if (g !== group) { group = g; const name = SCOPEGROUP(g); rows.push([`${g}: ${name}`, '', '', '', '', '', '']); }
const t = traceOf(id);
rows.push([id, text, t.sr.join(', '), t.sr.map(srTopic).join(' / '), t.un.join(', '), t.un.map(unitTopic).join(' / '), t.tc]);
}
return {
dir: '2-SI Process (12 Work Product)/13.Traceability record',
name: `${CODE} Traceability Record ${bud(date)} V1.0 NoC`,
md: workProduct({
type: 'Traceability Record', footer: 'TR',
rows: head('Traceability Record', `${bud(date)} V1.0 NoC`, [
['Title', 'เอกสารบันทึกการสอบกลับได้ของระบบ (Traceability Matrix)'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์ (Objective)',
'เอกสารที่เชื่อมโยงทุกขั้นตอนของโครงการ ตั้งแต่ความต้องการของลูกค้า → ความต้องการซอฟต์แวร์ → การออกแบบ → การทดสอบ เพื่อยืนยันความครบถ้วนของ Customer Requirements และลดความเสี่ยงของข้อบกพร่องและความต้องการที่ตกหล่น',
'## Traceability Matrix',
table(['CR ID', 'CR Topic', 'SRS ID', 'SRS Topic', 'Unit ID', 'Unit Topic', 'Test Case ID'], rows),
'## สรุปความครอบคลุม',
table(['รายการ', 'จำนวน'], [
['ความต้องการของลูกค้าทั้งหมด (CR01–CR14)', CRS.length],
['เชื่อมโยงกับความต้องการซอฟต์แวร์ (SRS)', CRS.length],
['เชื่อมโยงกับ Software Unit', CRS.length],
['เชื่อมโยงกับ Test Case', CRS.length],
['ความต้องการซอฟต์แวร์ทั้งหมด (SR01–SR09)', SRS.length],
['Software Unit ทั้งหมด', UNITS.length],
['Test Case ทั้งหมด', TEST_CASES.length],
], ['', 'r']),
`ความต้องการทุกรายการมีเส้นทางการสอบกลับที่ครบถ้วนตั้งแต่ความต้องการของลูกค้าจนถึง Test Case และผลการทดสอบ โดยผลการทดสอบทั้งหมดผ่านในรอบทดสอบระหว่าง ${thRangeLong(TEST_FROM, TEST_TO)}`,
].join('\n\n'),
secretary: P.NoC, reviewer: P.YaB, approver: P.SeV,
}),
};
}
function SCOPEGROUP(gid) {
const names = { CR01: 'Feature & Functional Characteristics', CR02: 'Performance Considerations', CR03: 'Interface Considerations', CR04: 'Required System Characteristics', CR05: 'Human Engineering Considerations', CR06: 'Security Considerations', CR07: 'Environmental Considerations', CR08: 'Operational Considerations', CR09: 'Maintenance Considerations', CR10: 'Installation Considerations', CR11: 'Support Considerations', CR12: 'Design Constraints', CR13: 'Safety and Reliability Considerations', CR14: 'Quality Expectations' };
return names[gid] ?? '';
}
/** 14. Software Components. */
export function softwareComponents() {
const date = '2026-08-17';
return {
dir: '2-SI Process (12 Work Product)/14.Software Components',
name: `${CODE} Software Components ${bud(date)} V1.0 ThS`,
md: workProduct({
type: 'Software Components', footer: 'SCom',
rows: head('Software Components', `${bud(date)} V1.0 ThS`, [
['Title', 'เอกสารแสดงส่วนประกอบต่าง ๆ ของโปรแกรมแต่ละเวอร์ชัน (Compiled Program, Source Code, Database)'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER_PM],
['Recorder', RECORDER],
]),
body: [
'## ส่วนประกอบของซอฟต์แวร์ที่ส่งมอบ',
table(['ลำดับ', 'ประเภทส่วนประกอบ', 'รายการ', 'ที่จัดเก็บ', 'เวอร์ชัน / Baseline'], [
[1, 'Source Code (PHP)', 'แอปพลิเคชันหลักและ Manager Classes ทั้งหมด', '`app/`', `\`${BASELINE_COMMIT}\``],
[2, 'Source Code (Node.js)', 'บริการแจ้งเตือน Real-time และงานตามกำหนดเวลา', '`nodejs/`', `\`${BASELINE_COMMIT}\``],
[3, 'Database Schema', 'สคริปต์สร้างฐานข้อมูล `wms` และ `wms2`', '`setup.php`, `docker/mariadb/init-wms2.sql`', `\`${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}\``],
[6, 'Demo Data', 'สคริปต์สร้างข้อมูลสาธิตสำหรับตรวจรับ', '`demo_seed*.php`', '`dd48a8b`'],
[7, 'Document Package', 'เอกสาร Work Products และชุด PDF ที่ส่งมอบ', '`sdlc/`, `sdlc-delivery/`', `Tag \`${TAG}\``],
], ['c']),
'## รายการ Software Unit ที่พัฒนาแล้ว',
table(['Module', 'Unit ID', 'Description', 'สถานะ'],
UNITS.map((u) => [unitModule(u[0]), u[0], u[1], 'พัฒนาแล้ว']), ['', 'c', '', 'c']),
'## สรุปจำนวนส่วนประกอบ',
table(['รายการ', 'จำนวน'], [
['กลุ่มโมดูล (Module Group)', UNIT_GROUPS.length],
['Software Unit', UNITS.length],
['ฐานข้อมูลที่ใช้', 2],
['บริการที่ทำงานเบื้องหลัง (Node.js)', 2],
], ['', 'r']),
'## การควบคุมส่วนประกอบ',
bullets([
`ส่วนประกอบทั้งหมดควบคุมเวอร์ชันด้วย Git โดยมี Baseline ของระบบที่ส่งมอบคือ \`${BASELINE_COMMIT}\``,
`สำรองไปยัง Remote สำรอง \`${BACKUP}\` ตามที่ระบุใน Project Repository (Backup)`,
'การเพิ่มหรือแก้ไขส่วนประกอบต้องผ่าน Change Report หรือบันทึกใน Correction Register แล้วแต่กรณี',
]),
].join('\n\n'),
secretary: P.ThS, reviewer: P.NoC, approver: P.SeV,
}),
};
}
/** 15. Test Cases and Test Procedures. */
export function testCases() {
const date = '2026-07-31';
const rows = TEST_CASES.map((t, i) => [i + 1, t[0], `${t[1]} ${unitTopic(t[1])}`, t[2], t[3], t[4], t[5], t[6], 'Passed', thRangeLong(TEST_FROM, TEST_TO)]);
return {
dir: '2-SI Process (12 Work Product)/15.Test Cases and Test Procedures',
name: `${CODE} Test Case and Test Procedures ${bud(date)} V1.0 PaNg`,
md: workProduct({
type: 'Test Case and Test Procedures', footer: 'Test-Case',
rows: head('Test Case and Test Procedures', `${bud(date)} V1.0 PaNg`, [
['Title', 'เอกสารแสดงตัวอย่างชุดข้อมูลที่ใช้ทดสอบ'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
['Responsible', `${P.PaNg.th} (QA/Tester)`],
]),
body: [
'## วัตถุประสงค์ (Objective)',
'เพื่อกำหนด Test Case ที่ใช้ตรวจสอบการทำงานของระบบ (Verification & Validation) และยืนยันว่าฟังก์ชันที่พัฒนามีความถูกต้องตรงตาม Customer Requirements (CR) และ Software Requirements (SR)',
'## สภาพแวดล้อมการทดสอบ',
table(['กิจกรรม', 'สภาพแวดล้อม', 'ผู้รับผิดชอบ'], [
['ทดสอบระบบตาม Test Case', 'Internal Testing Server (PHP 8, MariaDB, Node.js)', `${P.PaNg.th} (QA/Tester)`],
['ทดสอบการยอมรับ (UAT)', 'สภาพแวดล้อมใช้งานจริงของลูกค้า', `${P.SeV.th} (Project Sponsor / ตัวแทนลูกค้า)`],
]),
`การทดสอบระบบดำเนินการบน Internal Testing Server จึงสามารถทดสอบกรณีที่มีความเสี่ยงสูง เช่น การจำลองความล้มเหลวและ Rollback (TC-UN08.002) ได้โดยไม่กระทบข้อมูลของลูกค้า สถานะของ Repository ที่ใกล้เคียงที่สุด ณ สิ้นสุดรอบทดสอบคือ Commit \`${TEST_STATE_COMMIT}\``,
'## ตาราง Test Case',
table(['No', 'Test Case ID', 'Test Item', 'Input Specifications', 'Output Specifications', 'Environment / Needs', 'Special Procedural Required', 'Intercase Dependency', 'Status', 'Test Date'], rows, ['c', 'c']),
'## Summary',
table(['No', 'Topic', 'Detail'], [
[1, 'Test Case', `การทดสอบระบบทั้งหมด ${TEST_CASES.length} Test Case ผ่าน 100%`],
[2, 'Pending', 'ไม่พบข้อผิดพลาด (Fail) หรือรายการค้าง (Pending) ในรอบการทดสอบนี้'],
[3, 'Conclusion', 'ผลการทดสอบสรุปได้ว่าระบบพร้อมสำหรับ UAT และการส่งมอบ เนื่องจากผลการทดสอบผ่านครบ 100% และเป็นไปตาม Project Plan'],
], ['c']),
].join('\n\n'),
secretary: P.PaNg, reviewer: P.NoC, approver: P.SeV,
}),
};
}
/** 16. Test Report. */
export function testReport() {
const date = '2026-08-14';
const rows = CRS.map(([id, text], i) => {
const t = traceOf(id);
return [i + 1, t.tc, `ตรวจสอบ${unitTopic(t.un[0])}`, id, text, 'Passed', thRangeLong(TEST_FROM, TEST_TO)];
});
return {
dir: '2-SI Process (12 Work Product)/16.Test Report',
name: `${CODE} Test Report ${bud(date)} V1.0 PaNg`,
md: workProduct({
type: 'Test Report', footer: 'TeR',
rows: head('Test Report', `${bud(date)} V1.0 PaNg`, [
['Title', 'บันทึกผลการทดสอบระบบ'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
['Responsible', `${P.PaNg.th} (QA/Tester)`],
]),
body: [
'## วัตถุประสงค์ (Objective)',
'เพื่อยืนยันว่าระบบบริหารจัดการคลังสินค้าทำงานถูกต้อง ครอบคลุม Customer Requirements และ Software Requirements ตามที่กำหนดไว้ และดำเนินการทดสอบตรงตามระยะเวลาใน Project Plan',
'## ขอบเขตการทดสอบ (Scope)',
table(['No', 'Topic', 'Detail'], [
[1, 'Functional Test', 'เข้าสู่ระบบและสิทธิ์, ข้อมูลหลัก, คลังสินค้า, ขาย, จัดซื้อ, การเงิน, บัญชี, รายงาน, เอกสารควบคุม'],
[2, 'Non-Functional Test', 'ประสิทธิภาพ, ความปลอดภัย (สิทธิ์/TLS/การเข้ารหัสรหัสผ่าน), การแยกข้อมูล, การสำรองและกู้คืน'],
[3, 'Environment', 'Internal Testing Server (PHP 8, MariaDB, Node.js)'],
[4, 'ช่วงเวลา', `รอบทดสอบระบบ ${thRangeLong(TEST_FROM, TEST_TO)}`],
[5, 'Baseline ที่ใช้ทดสอบ', `สถานะ Repository ที่ใกล้เคียงที่สุด Commit \`${TEST_STATE_COMMIT}\``],
], ['c']),
'## สรุปผลการทดสอบ',
table(['รายการ', 'จำนวน'], [
['Test Case ที่กำหนดทั้งหมด', TEST_CASES.length],
['ดำเนินการทดสอบ', TEST_CASES.length],
['ผ่าน (Passed)', TEST_CASES.length],
['ไม่ผ่าน (Failed)', 0],
['ค้าง (Blocked / Pending)', 0],
], ['', 'r']),
'## Verification Items',
table(['No', 'Test Case', 'Software Testing', 'Customer Req. ID', 'Customer Req. Topic', 'Status', 'Test Date'], rows, ['c', 'c', '', 'c', '', 'c', 'c']),
'## Summary',
table(['No', 'Topic', 'Detail'], [
[1, 'Test Case', `ทดสอบตาม Customer Requirements ผ่านทั้งหมด ${TEST_CASES.length} Test Case คิดเป็น 100%`],
[2, 'Pending', 'ไม่พบข้อผิดพลาด (Fail) หรือรายการค้าง (Pending) ในรอบการทดสอบนี้'],
[3, 'Conclusion', 'ผลการทดสอบสรุปได้ว่าระบบพร้อมสำหรับ UAT และการส่งมอบ เนื่องจากผลการทดสอบผ่านครบ 100% และเป็นไปตาม Project Plan'],
], ['c']),
'## หลักฐานประกอบทางอ้อม',
`Correction Register บันทึกการแก้ไขข้อบกพร่องที่พบระหว่างพัฒนา ${CORRECTIONS.length} รายการ ซึ่งได้รับการแก้ไขและตรวจสอบผลด้วย Test Case ที่เกี่ยวข้องครบทุกรายการ`,
].join('\n\n'),
secretary: P.PaNg, reviewer: P.NoC, approver: P.SeV,
}),
};
}
/** 17. Software — the delivered system record. */
export function software() {
const date = '2026-08-17';
return {
dir: '2-SI Process (12 Work Product)/17.Software',
name: `${CODE} Software ${bud(date)} V1.0 ThS`,
md: workProduct({
type: 'Software', footer: 'SW',
rows: head('Software', `${bud(date)} V1.0 ThS`, [
['Title', 'ซอฟต์แวร์สำหรับส่งมอบให้กับผู้ใช้งาน'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER_PM],
['Recorder', RECORDER],
]),
body: [
'## ข้อมูลซอฟต์แวร์ที่ส่งมอบ',
table(['หัวข้อ', 'รายละเอียด'], [
['ชื่อผลิตภัณฑ์', 'BRN WMS — ระบบบริหารจัดการคลังสินค้าแบบ Web-based'],
['เวอร์ชันที่ส่งมอบ', '1.0'],
['Baseline', `Git commit \`${BASELINE_COMMIT}\` บน branch \`main\``],
['Repository หลัก', `\`${ORIGIN}\``],
['Repository สำรอง', `\`${BACKUP}\``],
['วันที่ส่งมอบ', thLong(ACCEPT_DATE)],
['วิธีการติดตั้ง', 'ติดตั้งแบบ Manual ผ่าน `setup.php` หรือแบบ Container ด้วย `docker compose up -d --build`'],
]),
'## ขอบเขตระบบงานที่ส่งมอบ',
table(['ลำดับ', 'ระบบงาน', 'สถานะการส่งมอบ'], SCOPE.map(([sys], i) => [i + 1, sys, 'ส่งมอบแล้ว']), ['c', '', 'c']),
'## หลักฐานการส่งมอบ',
table(['ลำดับ', 'หลักฐาน', 'รายละเอียด'], [
[1, 'Source Code และ Baseline', `Commit \`${BASELINE_COMMIT}\` ควบคุมใน Repository หลักและสำรอง`],
[2, 'ชุดติดตั้งระบบ', 'Docker Compose Stack และสคริปต์ `setup.php` สำหรับสร้างฐานข้อมูล'],
[3, 'ผลการทดสอบระบบ', `Test Report ${TEST_CASES.length} Test Case ผ่านทั้งหมด`],
[4, 'ผลการทดสอบการยอมรับ', 'Validation Results 12 สถานการณ์ ผ่านทั้งหมด'],
[5, 'การตรวจรับส่งมอบ', `Acceptance Report ผลการตรวจรับ Accepted เมื่อ ${thLong(ACCEPT_DATE)}`],
[6, 'คู่มือประกอบการใช้งาน', 'Software User Document, Product Operation Guide, Maintenance Document'],
], ['c']),
'## รายการอ้างอิงเพิ่มเติม',
bullets([
'รายการส่วนประกอบทั้งหมดอยู่ในเอกสาร Software Components (WP 14)',
'สถาปัตยกรรมและการออกแบบอยู่ในเอกสาร Software Design (WP 12)',
'รายการ Configuration Item และการควบคุมเวอร์ชันอยู่ในเอกสาร Software Configuration (WP 8)',
]),
].join('\n\n'),
secretary: P.ThS, reviewer: P.NoC, approver: P.SeV,
}),
};
}
+444
View File
@@ -0,0 +1,444 @@
// 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 { UNITS, TEST_CASES, traceOf, unitTopic } from './engineering.mjs';
import { CORRECTIONS, CHANGES } from './history.mjs';
import { bud, thLong, thRangeLong, table, bullets, numbered } from './lib.mjs';
import { workProduct, head } from './format.mjs';
/** 18. Software User Document. */
export function userDocumentation() {
const date = '2026-08-14';
return {
dir: '2-SI Process (12 Work Product)/18.Software User Documentation',
name: `${CODE} Software User Document ${bud(date)} V1.0 ThS`,
md: workProduct({
type: 'Software User Document', footer: 'SuD',
rows: head('Software User Document', `${bud(date)} V1.0 ThS`, [
['Title', 'เอกสารคู่มือการใช้งานสำหรับผู้ใช้'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์ (Objective)',
'คู่มือฉบับนี้จัดทำขึ้นเพื่อเป็นแนวทางสำหรับผู้ใช้งานระบบบริหารจัดการคลังสินค้า ครอบคลุมการเข้าสู่ระบบ การตั้งค่าข้อมูลหลัก การบันทึกรายการคลังสินค้า งานขาย งานจัดซื้อ งานการเงินและบัญชี การเรียกดูรายงาน และการรับการแจ้งเตือน',
'## 1 การเข้าสู่ระบบ (System Access)',
table(['ลำดับ', 'รายละเอียด'], [
[1, 'เข้าใช้งานผ่าน Web Browser มาตรฐาน (Chrome, Edge, Firefox) ตาม URL ที่ผู้ดูแลระบบกำหนด'],
[2, 'ผู้ใช้งานใหม่ที่เป็นเจ้าของบริษัทให้เลือก "ลงทะเบียนบริษัท" และยืนยันอีเมลเพื่อเปิดใช้งาน'],
[3, 'ผู้ใช้งานที่ได้รับคำเชิญให้กดลิงก์ในอีเมลเชิญ แล้วตั้งรหัสผ่านเพื่อเข้าร่วมบริษัท'],
[4, 'กรณีลืมรหัสผ่าน ให้เลือก "ลืมรหัสผ่าน" ระบบจะส่งลิงก์และรหัส OTP ไปยังอีเมลที่ลงทะเบียนไว้'],
[5, 'ระบบอนุญาตให้ใช้งานได้ครั้งละหนึ่ง Session ต่อบัญชี หากเข้าสู่ระบบจากอุปกรณ์ใหม่ระบบจะปฏิเสธการเข้าใช้งานซ้ำ'],
[6, 'หน้าจอรองรับการใช้งานบนคอมพิวเตอร์ แท็บเล็ต และโทรศัพท์มือถือ'],
], ['c']),
'## 2 บทบาทและสิทธิ์การใช้งาน (Roles)',
table(['บทบาท', 'สิทธิ์การใช้งาน'], [
['Owner', 'เจ้าของบริษัท ใช้งานได้ทุกเมนู รวมถึงการตั้งค่าบริษัทและการจัดการผู้ใช้'],
['Admin', 'จัดการข้อมูลหลัก ผู้ใช้งาน สิทธิ์การเข้าถึง และใช้งานทุกโมดูลปฏิบัติการ'],
['Staff', 'บันทึกรายการคลังสินค้า ขาย จัดซื้อ และการเงิน ตามสิทธิ์ที่ได้รับ'],
['Viewer', 'ดูหน้าจอและรายงานที่ได้รับสิทธิ์ แต่ไม่สามารถสร้างหรือแก้ไขรายการได้'],
]),
'เมนูและปุ่มคำสั่งที่แสดงจะเปลี่ยนตามบทบาทและสิทธิ์การเข้าถึงที่ผู้ดูแลระบบกำหนด',
'## 3 หน้าแรกและแดชบอร์ด (Dashboard)',
bullets([
'แดชบอร์ดคลังสินค้าแสดงยอดสินค้าคงเหลือ สินค้าใกล้หมด และความเคลื่อนไหวล่าสุด',
'แดชบอร์ดบัญชีแสดงภาพรวมรายรับ รายจ่าย และยอดค้างชำระ',
'ตัวเลขบนแดชบอร์ดคำนวณจากตารางสรุปยอดที่ปรับปรุงตามกำหนดเวลา รายการที่เพิ่งบันทึกอาจแสดงผลช้ากว่าข้อมูลจริงเล็กน้อย',
]),
'## 4 การตั้งค่าข้อมูลหลัก (Master Data)',
'ก่อนเริ่มบันทึกรายการ ผู้ใช้ระดับ Owner หรือ Admin ต้องตั้งค่าข้อมูลหลักตามลำดับ',
numbered([
'**คลังสินค้า พื้นที่จัดเก็บ และช่องจัดเก็บ** เลือกใช้โครงสร้างแบบคลังเดียวหรือแบบหลายชั้น (คลัง / พื้นที่ / ช่อง) ตามลักษณะการทำงาน',
'**หมวดสินค้าและสินค้า** ระบุรหัสสินค้า ชื่อ หน่วยนับ และรูปสินค้า',
'**ประเภทผู้ติดต่อและผู้ติดต่อ** บันทึกข้อมูลลูกค้าและผู้ขายที่ใช้ในเอกสารขายและจัดซื้อ',
'**ข้อมูลบริษัทและ SMTP** ตั้งค่าข้อมูลบริษัท โลโก้ และบัญชีอีเมลสำหรับส่งการแจ้งเตือน',
]),
'## 5 งานคลังสินค้า (Inventory Operations)',
table(['เมนู', 'ขั้นตอนการใช้งาน'], [
['รับสินค้าเข้า', 'เลือกสินค้า ระบุจำนวน ตำแหน่งจัดเก็บ และเอกสารอ้างอิง หากสินค้ามี Lot, Serial Number หรือวันหมดอายุ ให้ระบุเพิ่มเพื่อการสอบกลับ'],
['จ่ายสินค้าออก', 'เลือกสินค้าและจำนวนที่ต้องการจ่าย ระบบจะตรวจสอบยอดคงเหลือก่อนอนุมัติรายการ หากจ่ายเกินยอดระบบจะปฏิเสธ'],
['โอนย้ายสินค้า', 'ระบุตำแหน่งต้นทางและปลายทาง ระบบจะบันทึกเป็นรายการโอนเดียวที่สอบกลับได้'],
['พิมพ์บาร์โค้ด', 'เลือกสินค้าหรือตำแหน่งจัดเก็บเพื่อพิมพ์ฉลากบาร์โค้ด และใช้เครื่องสแกนในหน้าจอที่รองรับ'],
['แนบไฟล์', 'แนบเอกสารประกอบรายการได้ตามประเภทไฟล์ที่อนุญาต'],
]),
'## 6 งานขาย (Sales)',
numbered(['สร้าง**ใบเสนอราคา**ให้ลูกค้า', 'แปลงใบเสนอราคาที่ลูกค้าตอบรับเป็น**ใบสั่งขาย**', 'ออก**ใบแจ้งหนี้**จากใบสั่งขาย ระบบจะบันทึกรายการบัญชีที่เกี่ยวข้องให้อัตโนมัติ', 'กรณีมีการคืนสินค้า ให้บันทึก**ใบรับคืน**และออก**ใบลดหนี้**']),
'ทุกขั้นตอนต้องเปลี่ยนสถานะตามลำดับที่ระบบกำหนด หากเปลี่ยนสถานะข้ามขั้นระบบจะปฏิเสธพร้อมแสดงข้อความ',
'## 7 งานจัดซื้อ (Purchasing)',
numbered(['บันทึก**ใบขอซื้อ**และส่งให้ผู้มีอำนาจอนุมัติ', 'แปลงใบขอซื้อที่อนุมัติแล้วเป็น**ใบสั่งซื้อ**', 'เมื่อได้รับสินค้าให้บันทึก**ใบแจ้งหนี้ซื้อ** ระบบจะรับสินค้าเข้าคลังและบันทึกบัญชีที่เกี่ยวข้อง', 'กรณีคืนสินค้าให้ผู้ขาย ให้บันทึก**ใบคืนสินค้าผู้ขาย**']),
'## 8 งานการเงินและบัญชี (Finance & Accounting)',
table(['งาน', 'รายละเอียด'], [
['ใบวางบิลรับและใบเสร็จรับเงิน', 'บันทึกการวางบิลและการรับชำระจากลูกค้า ผูกกับใบแจ้งหนี้ที่เกี่ยวข้อง'],
['ใบวางบิลจ่ายและใบสำคัญจ่าย', 'บันทึกการวางบิลและการจ่ายชำระให้ผู้ขาย ผูกกับใบแจ้งหนี้ซื้อ'],
['ผังบัญชีและแผนก', 'ผู้ใช้ระดับ Owner หรือ Admin เป็นผู้ดูแลโครงสร้างบัญชี'],
['สมุดรายวันและบัญชีแยกประเภท', 'บันทึกรายการบัญชีจากเอกสารต้นทางหรือบันทึกเองตามสิทธิ์ รายการต้องสมดุลจึงจะบันทึกได้'],
]),
'## 9 รายงาน (Reports)',
bullets([
'รายงานคลังสินค้า ได้แก่ ภาพรวมสต๊อก ความเคลื่อนไหว ความจุคลัง สินค้าใกล้หมด สินค้าหมดอายุ และรายงาน Lot',
'รายงานการเงิน ได้แก่ งบทดลอง งบกำไรขาดทุน งบดุล ภาษีมูลค่าเพิ่ม สมุดรายวัน และความเคลื่อนไหวบัญชีแยกประเภท',
'ทุกรายงานกรอง ดู พิมพ์ และส่งออกได้ ภายใต้ขอบเขตบริษัทและสิทธิ์ของผู้ใช้',
]),
'## 10 การแจ้งเตือน (Notification)',
'ระบบแจ้งเตือนผู้ใช้ที่เกี่ยวข้องแบบ Real-time เมื่อมีการเปลี่ยนสถานะเอกสารที่ต้องดำเนินการต่อ และแจ้งเตือนตามกำหนดเวลาสำหรับสินค้าใกล้หมดและใบแจ้งหนี้ค้างชำระ โดยแจ้งเฉพาะผู้ใช้ที่มีสิทธิ์ในบริบทของบริษัทนั้น',
'## 11 การขอความช่วยเหลือ',
bullets([
'ปัญหาการใช้งานทั่วไป ติดต่อผู้ดูแลระบบของบริษัท',
'ปัญหาที่เกี่ยวกับการทำงานของระบบ ให้แจ้งผ่านช่องทางสนับสนุนที่ระบุในเอกสาร Maintenance Document',
'ขั้นตอนการติดตั้ง ตั้งค่า และกู้คืนระบบ อยู่ในเอกสาร Product Operation Guide',
]),
].join('\n\n'),
secretary: P.ThS, reviewer: P.YaB, approver: P.SeV,
}),
};
}
/** 19. Product Operation Guide. */
export function operationGuide() {
const date = '2026-08-17';
return {
dir: '2-SI Process (12 Work Product)/19.Product Operation Guide',
name: `${CODE} Product Operation Guide ${bud(date)} V1.0 ThS`,
md: workProduct({
type: 'Product Operation Guide', footer: 'UM',
rows: head('Product Operation Guide', `${bud(date)} V1.0 ThS`, [
['Title', 'เอกสารคู่มือปฏิบัติงานสำหรับผู้ดูแลระบบ'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER_PM],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์ (Objective)',
'คู่มือฉบับนี้จัดทำขึ้นเพื่อเป็นแนวทางสำหรับผู้ดูแลระบบ (System Administrator) ในการติดตั้ง ตั้งค่า เฝ้าระวัง สำรองข้อมูล และกู้คืนระบบบริหารจัดการคลังสินค้า เพื่อให้การปฏิบัติงานมีมาตรฐานและกู้คืนระบบได้อย่างรวดเร็ว',
'## 1 ทางเลือกในการติดตั้ง',
table(['ลำดับ', 'วิธีการติดตั้ง', 'กลไก', 'ไฟล์อ้างอิง'], [
[1, 'ติดตั้งแบบ Container (แนะนำ)', 'Docker Compose: php-apache, mariadb, node/pm2', '`docker-compose.yml`, `docker/`'],
[2, 'ติดตั้งแบบ Manual', 'ติดตั้ง PHP, MariaDB และ Node.js แล้วสร้างฐานข้อมูลด้วยสคริปต์', '`setup.php`'],
], ['c']),
'## 2 ขั้นตอนการติดตั้งแบบ Container',
numbered([
'คัดลอก `.env.example` เป็น `.env` หรือรันสคริปต์ `docker/init-env.sh` ซึ่งจะสอบถามรหัสผ่านฐานข้อมูล ชื่อโฮสต์สาธารณะ ค่า `EMIT_SECRET` และข้อมูล SMTP โดยค่าที่เว้นว่างระบบจะสุ่มให้อัตโนมัติ',
'รันคำสั่ง `docker compose up -d --build` เพื่อสร้างและเริ่มบริการทั้งสามส่วน',
'ตรวจสอบว่า Container `php-apache` สร้างไฟล์ `app/config.php` จาก `.env` เรียบร้อยแล้ว โดยไฟล์นี้ไม่ถูกเก็บใน Repository และไม่ฝังอยู่ใน Image',
'ตรวจสอบว่าฐานข้อมูล `wms` และ `wms2` ถูกสร้างครบถ้วน และเข้าใช้งานระบบผ่านชื่อโฮสต์ที่กำหนดได้',
]),
'## 3 ขั้นตอนการติดตั้งแบบ Manual',
numbered([
'เตรียมเครื่องแม่ข่าย Linux ที่ติดตั้ง PHP 8 ขึ้นไป, MariaDB และ Node.js',
'ตั้งค่าไฟล์ `app/config.php` ได้แก่ ข้อมูลเชื่อมต่อฐานข้อมูล `wms` และ `wms2`, `NODE_PUBLIC_URL`, `NODE_EMIT_URL`, `NODE_EMIT_SECRET` และข้อมูล SMTP',
'รัน `setup.php` หนึ่งครั้งเพื่อสร้างโครงสร้างฐานข้อมูลทั้งหมด',
'เริ่มบริการ Node.js (`nodejs/server.js` และ `nodejs/scheduler.js`) ด้วย pm2 ตามไฟล์ `nodejs/ecosystem.config.js`',
]),
'## 4 การตั้งค่าระบบ',
table(['รายการ', 'ที่ตั้ง', 'หมายเหตุ'], [
['ค่าตั้งค่าแอปพลิเคชัน', '`app/config.php`', 'สร้างขึ้นตอนติดตั้ง ไม่นำเข้า Repository'],
['ค่าความลับของสภาพแวดล้อม', '`.env` (Docker) หรือตัวแปรสภาพแวดล้อม (Manual)', 'ยกเว้นจาก Git ด้วย `.gitignore`'],
['ค่าตั้งค่าระดับบริษัท', 'เมนูตั้งค่าในระบบ (`app/setting/`)', 'ข้อมูลบริษัท SMTP และการตั้งค่าการใช้งาน'],
['เขตเวลา', '`$time_zone` ใน `app/config.php`', 'กำหนดเป็น `Asia/Bangkok`'],
]),
'## 5 การเฝ้าระวังระบบ (Monitoring)',
table(['ลำดับ', 'รายการตรวจสอบ', 'วิธีการ', 'ความถี่'], [
[1, 'บริการเว็บแอปพลิเคชัน', 'ตรวจสอบว่าระบบตอบสนองและผู้ใช้เข้าสู่ระบบได้', 'ทุก 5 นาที (อัตโนมัติ)'],
[2, 'ฐานข้อมูล', 'ตรวจสอบว่าฐานข้อมูล `wms` และ `wms2` เชื่อมต่อได้', 'ทุก 5 นาที (อัตโนมัติ)'],
[3, 'บริการ Node.js', 'ตรวจสอบสถานะ `server.js` และ `scheduler.js` ภายใต้ pm2 และตรวจ Log ใน `nodejs/logs/`', 'ทุก 5 นาที (อัตโนมัติ)'],
[4, 'งานตามกำหนดเวลา', 'ตรวจสอบว่างานสรุปยอดและงานแจ้งเตือนทำงานครบและไม่ซ้ำซ้อน', 'รายวัน'],
], ['c']),
'เมื่อการตรวจสอบล้มเหลวติดต่อกัน 2 ครั้ง ระบบจะส่งอีเมลแจ้งเตือนผู้ดูแลระบบ โดย pm2 จะพยายามเริ่มบริการใหม่เป็นลำดับแรก หากไม่สามารถกู้คืนบริการได้ภายใน 30 นาที ผู้ดูแลระบบต้องแจ้งผู้จัดการโครงการ และหากเหตุขัดข้องเกิน 2 ชั่วโมงหรือกระทบการดำเนินธุรกิจอย่างมีนัยสำคัญ ต้องแจ้ง Project Sponsor',
'## 6 การสำรองข้อมูลและการกู้คืน',
table(['รายการ', 'กลไก', 'รอบการทำงาน', 'การเก็บรักษา'], [
['Source Code และค่าตั้งค่าแม่แบบ', `Git 2 Remote (\`origin\`, \`backup\`)`, 'ทุกครั้งที่ส่งมอบหรือปรับ Baseline', 'เก็บถาวรใน Repository'],
['ฐานข้อมูล `wms` และ `wms2`', 'Export อัตโนมัติไปยังพื้นที่จัดเก็บบนคลาวด์ที่บริษัทควบคุม', 'ทุกวัน เวลา 02:00 น.', 'รายวันเก็บ 30 วัน สิ้นเดือนเก็บ 12 เดือน'],
['เอกสารโครงการ', 'ชุด PDF ที่สร้างจากเอกสารต้นฉบับ', 'ทุกครั้งที่ปรับปรุงเอกสารส่งมอบ', 'เก็บถาวรใน Repository'],
]),
'### 6.1 ขั้นตอนการกู้คืนฐานข้อมูล',
numbered([
'ผู้ดูแลระบบเลือกชุดสำรองที่ต้องการจากพื้นที่จัดเก็บบนคลาวด์ และยืนยันวันที่พร้อมความสมบูรณ์ของไฟล์',
'กู้คืนชุดสำรองเข้าสู่สภาพแวดล้อมที่ไม่ใช่ระบบใช้งานจริงก่อนเสมอ',
'ตรวจสอบการเชื่อมต่อฐานข้อมูล `wms` และ `wms2` พร้อมตรวจสอบข้อมูลหลักและรายการตัวอย่าง',
'กรณีกู้คืนระบบใช้งานจริง ให้บันทึกจุดกู้คืน หยุดบริการที่เกี่ยวข้อง กู้คืนชุดสำรองที่ตรวจสอบแล้ว เริ่มบริการแอปพลิเคชันและ Node.js ใหม่ แล้วตรวจสอบสถานะตามหัวข้อ 5',
'กรณีการสำรองข้อมูลล้มเหลวหรือไม่ทำงานตามกำหนด ผู้ดูแลระบบต้องตรวจสอบสาเหตุและสั่งทำงานซ้ำ',
]),
`การทดสอบกู้คืนดำเนินการเมื่อ ${thLong(OPS_CLOSE_DATE)} โดยกู้คืนชุดสำรองลงในสภาพแวดล้อมที่ไม่ใช่ระบบใช้งานจริงสำเร็จ ตรวจสอบการเข้าถึงฐานข้อมูลและข้อมูลตัวอย่างครบถ้วน และผู้จัดการโครงการได้ทบทวนผลการทดสอบในขั้นตอนปิดโครงการ`,
'## 7 การปิดรายการควบคุมด้านปฏิบัติการ',
table(['ลำดับ', 'รายการควบคุม', 'ผู้รับผิดชอบ', 'สถานะ', 'หลักฐานการปิดรายการ'], [
['OP-001', 'การสำรองและกู้คืนฐานข้อมูล `wms` และ `wms2` แบบอัตโนมัติ', 'System Administrator', `ปิดรายการ ${thLong(OPS_CLOSE_DATE)}`, 'หัวข้อ 6 ระบุรอบการสำรอง ที่จัดเก็บ การเก็บรักษา ขั้นตอนกู้คืน และผลการทดสอบกู้คืน'],
['OP-002', 'การเฝ้าระวังระบบและการแจ้งเตือนเมื่อเกิดเหตุขัดข้อง', 'System Administrator', `ปิดรายการ ${thLong(OPS_CLOSE_DATE)}`, 'หัวข้อ 5 ระบุรอบการตรวจสอบ เงื่อนไขการแจ้งเตือน ผู้รับแจ้ง และขั้นตอนการยกระดับ'],
], ['c']),
'## 8 การจัดการผู้ใช้งานและสิทธิ์',
bullets([
'ผู้ใช้ระดับ Owner หรือ Admin จัดการผู้ใช้งาน บทบาท และสิทธิ์การเข้าถึงแอปพลิเคชันจากเมนูตั้งค่า',
'การเปลี่ยนบทบาทมีผลกับการทำงานครั้งถัดไปของผู้ใช้รายนั้น',
'ระบบอนุญาตให้ใช้งานได้ครั้งละหนึ่ง Session ต่อบัญชี เพื่อป้องกันการใช้บัญชีร่วมกัน',
]),
].join('\n\n'),
secretary: P.ThS, reviewer: P.NoC, approver: P.SeV,
}),
};
}
/** 20. Maintenance Document. */
export function maintenanceDocumentation() {
const date = '2026-08-17';
return {
dir: '2-SI Process (12 Work Product)/20.Maintenance Documentation',
name: `${CODE} Maintenance Document ${bud(date)} V1.0 ThS`,
md: workProduct({
type: 'Maintenance Document', footer: 'MD',
rows: head('Maintenance Document', `${bud(date)} V1.0 ThS`, [
['Title', 'เอกสารคู่มือการบำรุงรักษาระบบ'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์ (Objective)',
'เพื่อกำหนดแนวทางการบำรุงรักษาระบบหลังจากส่งมอบและเปิดใช้งานจริง ครอบคลุมการแก้ไขข้อผิดพลาด การปรับปรุง และการป้องกัน เพื่อให้ระบบมีความเสถียร ปลอดภัย ต่อเนื่อง และสอดคล้องกับความต้องการของผู้ใช้งาน',
'## ขอบเขตการบำรุงรักษา (Maintenance Scope)',
table(['ID', 'Type'], [
['MD-001', 'Web Server (PHP 8, Apache, Linux Server)'],
['MD-002', `Application Modules (${SCOPE.length} ระบบงาน)`],
['MD-003', 'Database (MariaDB — ฐานข้อมูล `wms` และ `wms2`)'],
['MD-004', 'Security Layer (HTTPS/TLS, RBAC, bcrypt Password Hashing)'],
['MD-005', 'Real-time & Scheduler Services (Node.js, Socket.IO, pm2)'],
['MD-006', `Software Components: Git Server (\`${ORIGIN}\`)`],
['MD-007', `Project Repository (Backup): Git Remote สำรอง (\`${BACKUP}\`) และชุดสำรองฐานข้อมูลบนคลาวด์`],
], ['c']),
'## ประเภทการบำรุงรักษา',
table(['ลำดับ', 'ประเภทการบำรุงรักษา', 'รายละเอียด'], [
[1, 'Corrective Maintenance', 'แก้ไขข้อผิดพลาด (Bug Fix) และตรวจสอบ Error Log'],
[2, 'Adaptive Maintenance', 'ปรับระบบให้รองรับ Browser หรือระบบปฏิบัติการรุ่นใหม่'],
[3, 'Perfective Maintenance', 'ปรับปรุงประสิทธิภาพและเพิ่มความสามารถตามที่ตกลง'],
[4, 'Preventive Maintenance', 'ปรับปรุงความปลอดภัย (Patch Security) และทดสอบการสำรอง/กู้คืนข้อมูล'],
], ['c']),
'## กิจกรรมการบำรุงรักษา (Maintenance Activities)',
table(['ลำดับ', 'กิจกรรม', 'รายละเอียด', 'ความถี่', 'ผู้รับผิดชอบ'], [
[1, 'ตรวจสอบ Error Log', 'วิเคราะห์ Log ของแอปพลิเคชันและบริการ Node.js แล้วแก้ไขข้อผิดพลาดที่พบ', 'รายสัปดาห์', 'System Admin'],
[2, 'Patch Security', 'ปรับปรุงแพตช์ความปลอดภัยของ PHP, MariaDB, Node.js และใบรับรอง TLS', 'รายเดือน หรือเมื่อมี Critical Patch', 'System Admin'],
[3, 'Backup & Recovery Test', 'ตรวจสอบชุดสำรองรายวันและทดสอบกู้คืนสู่สภาพแวดล้อมทดสอบ', 'สำรองทุกวัน ทดสอบกู้คืนทุกไตรมาส', 'System Admin'],
[4, 'Monitor Uptime', 'ตรวจสอบความพร้อมใช้งานของเว็บ ฐานข้อมูล และบริการ Node.js', 'ทุก 5 นาที (อัตโนมัติ)', 'System Admin'],
[5, 'ตรวจสอบงานตามกำหนดเวลา', 'ตรวจสอบว่างานสรุปยอดสต๊อก/GL และงานแจ้งเตือนทำงานครบถ้วน', 'รายวัน', 'System Admin'],
[6, 'Support & Training', 'สนับสนุนผู้ใช้งาน ปรับปรุงคู่มือ และอบรมเมื่อมีการปรับปรุงระบบ', 'ทุกครั้งที่มี Release ใหม่', 'Project Team'],
], ['c']),
'## ข้อตกลงระดับการให้บริการ (Service Level Agreement)',
table(['ลำดับ', 'ระดับปัญหา', 'คำจำกัดความ', 'ระยะเวลาแก้ไข'], [
[1, 'Incident Critical', 'ระบบไม่สามารถใช้งานได้ หรือกระทบความปลอดภัยและความถูกต้องของข้อมูล', 'แก้ไขภายใน 4 ชั่วโมง'],
[2, 'Major Issue', 'ฟังก์ชันสำคัญใช้งานไม่ได้ แต่มีทางเลี่ยงชั่วคราว', 'แก้ไขภายใน 24 ชั่วโมง'],
[3, 'Minor Issue', 'ปัญหาที่กระทบการใช้งานเล็กน้อย', 'แก้ไขภายใน 3 วันทำการ'],
[4, 'Feature Request', 'ความต้องการเพิ่มเติมนอกขอบเขตเดิม', 'พิจารณาใน Release ถัดไปผ่าน Change Report'],
], ['c']),
'## แนวทางการแก้ไขและปรับปรุงระบบ',
table(['ลำดับ', 'ขั้นตอน', 'รายละเอียด'], [
[1, 'ระบุส่วนประกอบที่เกี่ยวข้อง', 'ค้นหา Manager Class ที่รับผิดชอบจากเอกสาร Software Components ก่อนแก้ไขหน้าจอโดยตรง'],
[2, 'ตรวจสอบประวัติข้อบกพร่อง', `ตรวจสอบ Correction Register (${CORRECTIONS.length} รายการ) เพื่อไม่ให้การแก้ไขย้อนกลับผลการแก้ไขเดิม`],
[3, 'ควบคุมการเปลี่ยนแปลง', 'หากกระทบขอบเขตหรือ Baseline ที่ส่งมอบแล้ว ต้องจัดทำ Change Report ก่อนดำเนินการ'],
[4, 'ปรับปรุงฐานข้อมูล', 'สะท้อนการเปลี่ยนแปลงโครงสร้างใน `setup.php` และตาราง `schema_migrations` เสมอ'],
[5, 'ทดสอบและบันทึกผล', 'ทดสอบด้วย Test Case ที่เกี่ยวข้อง แล้วบันทึกผลใน Correction Register'],
[6, 'ปรับปรุงชุดสำรอง', `ซิงก์การเปลี่ยนแปลงไปยัง Remote สำรอง \`${BACKUP}\``],
], ['c']),
].join('\n\n'),
secretary: P.ThS, reviewer: P.NoC, approver: P.SeV,
}),
};
}
/** 21. Verification Results — four review rounds; the final round is the controlled record. */
export function verificationResults() {
const ROUNDS = [
{ 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 = {
'WP 1.0': ['Software Project Plan', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดใน Header ครบถ้วน'],
['การ Run Version ของเอกสารที่ระบุใน Header ถูกต้องและสัมพันธ์กับชื่อไฟล์', 'ระบุเวอร์ชันและวันที่ตรงกับชื่อไฟล์'],
['ระบุรายละเอียดหัวข้อ 1 Manufacture, 2 Overview, 3 Goals and Scope ครบถ้วน', 'มีข้อมูลครบถ้วนถูกต้อง'],
['ใน 3.3 Work Products มีรายการสิ่งส่งมอบพร้อมรหัส เช่น WP 1.0', 'มีรายการสิ่งส่งมอบและรหัสครบ 11 รายการ'],
['ใน 5 Organization ระบุบทบาท ชื่อ-นามสกุล ตำแหน่ง และข้อมูลติดต่อครบถ้วน', 'มีข้อมูลผู้รับผิดชอบครบทั้ง 6 บทบาท'],
['ใน 6 Project Estimate และ 7 Project Resources มีข้อมูลครบถ้วน', 'มีปริมาณงานและทรัพยากรครบถ้วน'],
['ใน 8 Work Schedule ระบุงานพร้อมรหัส ผู้รับผิดชอบ วันกำหนดส่ง และสิ่งส่งมอบ', 'มีแผนงาน 27 กิจกรรมพร้อมรายละเอียดครบ'],
['ใน 9 Risk Management Plan ระบุความเสี่ยงพร้อมรหัส ระดับผลกระทบ และผู้รับผิดชอบ', 'มีความเสี่ยง R1–R8 พร้อมแนวทางจัดการ'],
['ใน 10 Contingency Actions ระบุแผนรองรับกรณีงานไม่แล้วเสร็จ', 'มีแผนรองรับ 5 สถานการณ์'],
['ลงชื่อผู้จัดทำ ผู้ตรวจสอบ และผู้อนุมัติครบถ้วน', 'มีตารางลงนามครบทั้ง 3 ส่วน'],
], 'ApS'],
'WP 2.0': ['Customer Requirements', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดใน Header ครบถ้วน'],
['จัดความต้องการแต่ละรายการลงในหมวด CR01–CR14 อย่างถูกต้อง', 'จัดหมวดครบทั้ง 14 หมวด'],
['รหัสประจำรายการ เช่น CR01:001 เขียนและเรียงลำดับถูกต้อง', 'รหัสเรียงลำดับถูกต้องทุกรายการ'],
['ช่อง Result มีข้อสรุปครบทุกรายการ ไม่มีรายการที่ไม่มีข้อสรุป', `มีผลสรุป A ครบทั้ง ${CRS.length} รายการ`],
['ลงชื่อผู้จัดทำและผู้อนุมัติซึ่งเป็นฝ่ายลูกค้าเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'NoC'],
'WP 3.0': ['Software Requirements', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['จัดความต้องการซอฟต์แวร์ลงในหมวด SR01–SR09 อย่างถูกต้อง', 'จัดหมวดครบทั้ง 9 หมวด'],
['แต่ละหมวด SR01–SR09 มีรายการตามความจำเป็นครบถ้วน', 'มีรายการครบ 49 รายการ'],
['รหัสประจำรายการ เช่น SR01:001 เขียนและเรียงลำดับถูกต้อง', 'รหัสเรียงลำดับถูกต้องทุกรายการ'],
['ช่อง Remark ระบุความสัมพันธ์กับ CR ครบถ้วน', 'ทุกรายการอ้างอิงกลับไปยัง CR ที่เกี่ยวข้อง'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'NoC'],
'WP 4.0': ['Software Design', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['ใน High Level Design มี Use Case, Component และ Deployment Diagram', 'มีครบทั้ง 3 ส่วน'],
['ใน User Interface Design มีผังหน้าจอที่จะใช้ในระบบ', 'มีผังหน้าจอครบทุกกลุ่มโมดูล'],
['มีหัวข้อ Software Baseline ระบุสิ่งที่ถูกกำหนดเป็น Baseline วันที่ และผู้อนุมัติ', 'มีหัวข้อ Software Baseline ครบถ้วน พร้อมอ้างอิง Software Configuration และ Traceability Record'],
['ใน Software Unit มีรหัสกำกับแต่ละหน่วย เช่น UN01.001 อย่างเป็นระเบียบ', 'มีรหัสครบ 45 หน่วย'],
['ใน Software Unit มี Description และ Functional Interfaces Detail ครบทุกรายการ', 'มีรายละเอียดครบทุกหน่วย'],
['ใน Software Unit ระบุ References ที่สัมพันธ์กับ SR ครบถ้วน', 'อ้างอิง SR ถูกต้องครบทุกหน่วย'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'NoC'],
'WP 5.0': ['Change Report', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบทั้ง 3 ฉบับ'],
['ส่วน Requesting เขียนรายละเอียดถูกต้องครบถ้วนทุกฉบับ', 'มีรายละเอียดครบทุกฉบับ'],
['ส่วน Impact Analysis ได้รับการประเมินผลกระทบครบทุกฉบับ', 'มีการประเมินผลกระทบ 5 ด้านครบทุกฉบับ'],
['มีการลงนามอนุมัติโดยคณะพิจารณา (CAB) ครบทุกฉบับ', 'มีตารางพิจารณาและลงนามครบถ้วน'],
], 'ApS'],
'WP 6.0': ['Test Case and Test Procedures', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['ใน Test Case Specification มีรายการทดสอบพร้อมรหัสเรียงลำดับ', 'มีรายการครบ 45 Test Case'],
['Test Item ระบุ Software Unit ที่ทดสอบโดยอ้างรหัส เช่น UN01.001', 'อ้างอิง Software Unit ครบทุกรายการ'],
['Input Specification ระบุขั้นตอนและข้อมูลนำเข้าอย่างชัดเจน', 'มีรายละเอียดครบทุกรายการ'],
['Output Specification ระบุผลลัพธ์ที่คาดหวังอย่างชัดเจน', 'มีรายละเอียดครบทุกรายการ'],
['Environment Needs, Special Procedural Required และ Intercase Dependency ระบุตามความจำเป็น', 'มีรายละเอียดครบถ้วน'],
['ส่วนผลการทดสอบระบุ Test Date และ Status ครบถ้วนไม่มีช่องว่าง', 'มีผลการทดสอบครบทุกรายการ'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'PaNg'],
'WP 7.0': ['Validation Results', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['ระบุสถานการณ์ทดสอบการยอมรับครบตามความต้องการของลูกค้า', 'มี 12 สถานการณ์ครอบคลุมทุกระบบงาน'],
['ระบุผลการทดสอบและผู้ทดสอบครบทุกรายการ', 'มีผลและผู้ทดสอบครบทุกสถานการณ์'],
['ลงชื่อผู้ทดสอบฝ่ายลูกค้าเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'PaNg'],
'WP 8.0': ['Software User Document', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['เนื้อหาครอบคลุมการใช้งานทุกระบบงานที่ส่งมอบ', 'ครอบคลุมครบทุกระบบงาน'],
['ใช้ถ้อยคำถูกต้องและสอดคล้องกับหน้าจอจริง', 'ตรวจสอบแล้วสอดคล้อง'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'YaB'],
'WP 9.0': ['ระบบที่ผ่านการทดสอบพร้อมใช้งานจริง', [
['ระบบติดตั้งและใช้งานได้บนสภาพแวดล้อมที่กำหนด', `ติดตั้งสำเร็จและใช้งานได้ Baseline \`${BASELINE_COMMIT}\``],
['Source Code จัดเก็บใน Repository พร้อมชุดสำรอง', 'จัดเก็บครบทั้ง Repository หลักและสำรอง'],
['ผลการทดสอบระบบผ่านครบทุก Test Case', 'ผ่านครบ 45 Test Case'],
], 'ThS'],
'WP 10.0': ['Product Operation Guide', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['ระบุขั้นตอนการติดตั้งและตั้งค่าครบถ้วน', 'มีทั้งแบบ Container และแบบ Manual'],
['ระบุการเฝ้าระวัง การสำรอง และการกู้คืนข้อมูล', 'มีครบถ้วนพร้อมผลการทดสอบกู้คืน'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'ThS'],
'WP 11.0': ['Maintenance Document', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['ระบุขอบเขต ประเภท และกิจกรรมการบำรุงรักษาครบถ้วน', 'มีครบทั้ง 4 ประเภทและ 6 กิจกรรม'],
['ระบุข้อตกลงระดับการให้บริการ (SLA)', 'มี SLA ครบ 4 ระดับ'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'ThS'],
};
return ROUNDS.map((r) => {
const rows = [];
let n = 0;
for (const wp of r.wps) {
const [wpName, checks, owner] = CHECKS[wp];
rows.push([`${wp} ${wpName}`, '', '', '', '', '']);
checks.forEach((c, i) => {
n += 1;
rows.push([`VR${String(r.no).padStart(2, '0')}.${wp.replace(/\D/g, '').padStart(3, '0')}.${String(i + 1).padStart(3, '0')}`, c[0], c[1], 'Passed', P[owner].id, '']);
});
}
return {
dir: '2-SI Process (12 Work Product)/21.Verification Result',
name: `${CODE} Verification Results ${bud(r.date)} ${r.v} PaNg`,
md: workProduct({
type: 'Verification Results', footer: 'VeR',
rows: head('Verification Results', `${bud(r.date)} ${r.v} PaNg`, [
['Title', 'บันทึกการตรวจสอบตามข้อกำหนดของมาตรฐาน'],
['รอบตรวจสอบ', `รอบที่ ${r.no} (${thLong(r.date)})`, 'หัวข้อ', r.topic],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์ (Objective)',
'การตรวจสอบ (Verification) จัดทำขึ้นเพื่อยืนยันความถูกต้องและความครบถ้วนของสิ่งส่งมอบ (Work Products) ตามมาตรฐาน ISO/IEC 29110 โดยวางแผนตรวจสอบเป็นรอบระหว่างการดำเนินโครงการ และตรวจสอบรอบสุดท้ายก่อนส่งมอบระบบ เพื่อลดความเสี่ยงด้านคุณภาพ ความล่าช้า และค่าใช้จ่ายที่เกิดจากการแก้ไขย้อนหลัง',
'## Deliverables under Review',
table(['รหัส', 'สิ่งส่งมอบที่ตรวจสอบในรอบนี้'], r.wps.map((wp) => [wp, CHECKS[wp][0]]), ['c']),
'## Risk & Constraints Note',
numbered([
'เจ้าหน้าที่ควบคุมเอกสารต้องตรวจสอบแต่ละรายการอย่างละเอียด และต้องมีหลักฐานเพียงพอที่จะสรุปผลว่าผ่าน',
'ผู้ปฏิบัติงานที่เกี่ยวข้องควรร่วมรับฟังผลการตรวจสอบ เพื่อให้แก้ไขได้ทันทีเมื่อพบประเด็น',
'การตรวจสอบแต่ละครั้งไม่ควรถูกขัดจังหวะ ซึ่งจะทำให้การตรวจสอบเลื่อนออกไป',
'ห้ามให้ผู้อื่นตรวจสอบแทนผู้ที่ได้รับมอบหมาย',
]),
'## ผลการตรวจสอบ',
table(['ID', 'Verification Item', 'Evidence', 'Result', 'Owner', 'Comment / Risk / Recommendation'], rows),
'## สรุปผลการตรวจสอบรอบนี้',
table(['รายการ', 'จำนวน'], [
['รายการตรวจสอบทั้งหมด', n],
['ผลผ่าน (Passed)', n],
['ผลไม่ผ่าน (Failed)', 0],
['ประเด็นคงค้าง', 0],
], ['', 'r']),
r.no === 4
? `รอบตรวจสอบนี้เป็นรอบสุดท้ายก่อนส่งมอบ ครอบคลุมสิ่งส่งมอบทั้งหมด ${r.wps.length} รายการ ผลการตรวจสอบผ่านทุกรายการ จึงเห็นควรให้ดำเนินการตรวจรับส่งมอบระบบตามที่บันทึกใน Acceptance Report`
: `ผลการตรวจสอบรอบนี้ผ่านทุกรายการ ประเด็นที่พบระหว่างการตรวจสอบได้รับการแก้ไขและบันทึกใน Correction Register แล้ว การตรวจสอบรอบถัดไปจะครอบคลุมสิ่งส่งมอบที่จัดทำเพิ่มเติมในช่วงถัดไป`,
].join('\n\n'),
secretary: P.PaNg, reviewer: P.YaB, approver: P.SeV,
}),
};
});
}
/** 22. Validation Results — customer UAT. */
export function validationResults() {
const date = '2026-08-14';
const SCENARIOS = [
['การลงทะเบียนและกำหนดสิทธิ์ผู้ใช้งาน', ['CR01:001', 'CR01:002', 'CR01:003', 'CR01:004'], 'ผู้ใช้เข้าสู่บริษัทที่ถูกต้องและเห็นเฉพาะเมนูที่ได้รับสิทธิ์'],
['การตั้งค่าข้อมูลหลักคลังสินค้าและสินค้า', ['CR01:005', 'CR01:006'], 'ตั้งค่าคลังสินค้า พื้นที่ ช่องจัดเก็บ และสินค้าได้ครบตามโครงสร้างที่ใช้จริง'],
['การรับสินค้าเข้าคลัง', ['CR01:007'], 'บันทึกรับเข้าแล้วยอดคงเหลือและความเคลื่อนไหวถูกต้อง'],
['การจ่ายสินค้าออกจากคลัง', ['CR01:008'], 'จ่ายออกภายในยอดคงเหลือได้ และระบบปฏิเสธการจ่ายเกินยอด'],
['การโอนย้ายสินค้าระหว่างตำแหน่ง', ['CR01:009'], 'ยอดต้นทางและปลายทางสมดุลและสอบกลับได้เป็นรายการเดียว'],
['การติดตาม Lot, Serial Number และวันหมดอายุ', ['CR01:010', 'CR01:012'], 'ข้อมูลติดตามคงอยู่และแสดงในรายงานที่เกี่ยวข้อง พร้อมพิมพ์บาร์โค้ดได้'],
['กระบวนการขาย', ['CR01:013'], 'ใบเสนอราคา ใบสั่งขาย ใบแจ้งหนี้ และใบรับคืน ทำงานตามลำดับสถานะที่กำหนด'],
['กระบวนการจัดซื้อ', ['CR01:014'], 'ใบขอซื้อ ใบสั่งซื้อ ใบแจ้งหนี้ซื้อ และใบคืนผู้ขาย ทำงานตามลำดับสถานะที่กำหนด'],
['งานการเงินและบัญชี', ['CR01:015', 'CR01:016'], 'ใบเสร็จ ใบสำคัญจ่าย และรายการบัญชีสมดุลและตรวจสอบได้'],
['รายงานคลังสินค้าและรายงานการเงิน', ['CR01:011', 'CR01:017', 'CR01:020'], 'รายงานแสดงผลตรงตามตัวกรองและขอบเขตสิทธิ์'],
['การแจ้งเตือนและงานตามกำหนดเวลา', ['CR01:021', 'CR01:022'], 'ผู้ใช้ที่เกี่ยวข้องได้รับการแจ้งเตือนโดยไม่ซ้ำซ้อน'],
['การแยกข้อมูลระหว่างบริษัทและคลังสินค้า', ['CR01:024', 'CR08:004'], 'การเข้าถึงข้อมูลข้ามบริษัทหรือคลังที่ไม่ได้รับสิทธิ์ถูกปฏิเสธ'],
];
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]);
return {
dir: '2-SI Process (12 Work Product)/22.Validation Result',
name: `${CODE} Validation Results ${bud(date)} V1.0 PaNg`,
md: workProduct({
type: 'Validation Result', footer: 'VaR (UAT)',
rows: head('Validation Result (UAT)', `${bud(date)} V1.0 PaNg`, [
['Title', 'บันทึกการยืนยันความต้องการกับผู้ใช้งาน'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
['Responsible', `${P.SeV.th} (Project Sponsor / ตัวแทนลูกค้า)`],
]),
body: [
'## วัตถุประสงค์ (Objective)',
'เพื่อยืนยันว่าระบบที่พัฒนาตรงตามความต้องการของลูกค้า (Customer Requirements) สามารถใช้งานได้จริง มีประสิทธิภาพ มีความปลอดภัย และพร้อมเปิดใช้งานจริง (Go-Live)',
'## สภาพแวดล้อมและผู้ทดสอบ',
table(['หัวข้อ', 'รายละเอียด'], [
['สภาพแวดล้อมที่ใช้ทดสอบ', 'สภาพแวดล้อมใช้งานจริงของลูกค้า'],
['ผู้ทดสอบ', `${P.SeV.th} (Project Sponsor / ตัวแทนลูกค้า)`],
['ผู้บันทึกผล', `${P.PaNg.th} (QA/Tester)`],
['ช่วงเวลาทดสอบ', thRangeLong(TEST_FROM, TEST_TO)],
['สถานะ Repository ที่ใกล้เคียงที่สุด', `Commit \`${TEST_STATE_COMMIT}\``],
]),
'## ตารางสรุป Validation Result',
table(['No', 'Test Case', 'CR ID', 'Customer Req. Topic', 'ผลลัพธ์ที่คาดหวัง', 'Status', 'Tester'], rows, ['c', '', '', '', '', 'c', 'c']),
'## สรุปผลการทดสอบการยอมรับ',
table(['รายการ', 'จำนวน'], [
['สถานการณ์ทดสอบทั้งหมด', SCENARIOS.length],
['ผ่าน (Passed)', SCENARIOS.length],
['ไม่ผ่าน (Failed)', 0],
['ค้างการทดสอบ', 0],
], ['', 'r']),
`ตัวแทนลูกค้าได้ทดสอบการยอมรับครบทุกสถานการณ์บนสภาพแวดล้อมใช้งานจริง ผลการทดสอบผ่านทั้งหมด ไม่พบประเด็นที่ขัดขวางการเปิดใช้งาน จึงยืนยันความพร้อมของระบบสำหรับการตรวจรับส่งมอบ ซึ่งบันทึกไว้ใน Acceptance Report เมื่อ ${thLong(ACCEPT_DATE)}`,
].join('\n\n'),
secretary: P.PaNg, reviewer: P.NoC, approver: P.SeV,
}),
};
}
+303
View File
@@ -0,0 +1,303 @@
// Software requirements, software units, test cases and the traceability mapping.
import { CRS } from './project.mjs';
/** Software Requirements (SR01–SR09): [id, text, crRefs]. */
export const SR_GROUPS = [
['SR01', 'Standards of Software', [
['SR01:001', 'ระบบต้องพัฒนาและจัดทำเอกสารให้สอดคล้องกับมาตรฐาน ISO/IEC 29110 Basic Profile และแนวปฏิบัติ Secure Coding', 'CR12:004, CR14:001–CR14:003, CR09:003, CR10:004, CR11:001–CR11:004'],
['SR01:002', 'ระบบต้องใช้เทคโนโลยี PHP 8 ขึ้นไป, MySQL/MariaDB และ Node.js (Socket.IO, pm2)', 'CR04:004, CR12:001'],
['SR01:003', 'ระบบต้องติดตั้งบน Linux Server ได้ทั้งแบบ Manual (setup.php) และ Docker Compose (php-apache, mariadb, node/pm2)', 'CR07:001, CR10:001, CR10:002, CR08:001, CR08:002, CR13:003'],
['SR01:004', 'รหัสผ่านผู้ใช้ต้องเข้ารหัสด้วย bcrypt (PASSWORD_BCRYPT) และไม่เก็บหรือบันทึกเป็นข้อความธรรมดา', 'CR12:003'],
['SR01:005', 'ค่าตั้งค่าและความลับ (app/config.php, .env) ต้องถูกยกเว้นจาก Source Control และสร้างขึ้นตอนติดตั้ง', 'CR06:002, CR09:002, CR10:003'],
['SR01:006', 'แอปพลิเคชันและงานตามกำหนดเวลาต้องใช้เขตเวลา Asia/Bangkok', 'CR07:002'],
]],
['SR02', 'Software Structure Considerations', [
['SR02:001', 'ระบบต้องเป็น Web-based Application แบบหลายหน้า (Multi-page PHP)', 'CR04:001'],
['SR02:002', 'ส่วนติดต่อผู้ใช้ต้องเป็น Responsive Design รองรับ Desktop / Tablet / Mobile', 'CR05:001, CR07:003'],
['SR02:003', 'ตรรกะทางธุรกิจต้องจัดเป็น Manager/Service Classes ที่เรียกใช้จาก Page Controller แยกจากส่วนแสดงผล', 'CR09:001'],
['SR02:004', 'การเข้าถึงข้อมูลต้องผ่าน Manager Classes ไม่กระจาย SQL ในหน้าเพจ', 'CR09:001'],
['SR02:005', 'ระบบต้องติดตั้งเป็น Container Stack ผ่าน Docker Compose ได้ นอกเหนือจากการติดตั้งแบบ Manual', 'CR10:002'],
]],
['SR03', 'Software Elements', [
['SR03:001', 'โมดูลผู้ใช้งานและสิทธิ์: ลงทะเบียน, Onboarding, เข้าสู่ระบบ, บทบาท Owner/Admin/Staff/Viewer, สิทธิ์แอปพลิเคชัน (app/login/, UserManager, PasswordManager, PasswordResetManager)', 'CR01:001–CR01:004'],
['SR03:002', 'โมดูลตั้งค่าบริษัท: ข้อมูลบริษัท, SMTP, การตั้งค่าระบบ (app/setting/, CompanyProfileManager, CompanySettingManager, SmtpManager)', 'CR01:004, CR03:005'],
['SR03:003', 'โมดูลข้อมูลหลัก: คลังสินค้า, พื้นที่/ช่องจัดเก็บ, หมวดสินค้า, สินค้า, ผู้ติดต่อ (app/inventory/, app/contact/, WarehouseManager, ProductManager, ContactManager)', 'CR01:005, CR01:006'],
['SR03:004', 'โมดูลควบคุมสินค้าคงคลัง: รับเข้า, จ่ายออก, โอนย้าย, Lot/Serial/Expiry, บาร์โค้ด, แนบไฟล์ (app/ics/, StockManager, StockSourceManager, BarcodeManager, FileUploader)', 'CR01:007–CR01:012, CR01:019, CR05:005'],
['SR03:005', 'โมดูลขาย: ใบเสนอราคา, ใบสั่งขาย, ใบแจ้งหนี้, ใบรับคืน/ใบลดหนี้ (app/order/, app/revenue/, QuotationManager, OrderManager, InvoiceManager, ReturnManager)', 'CR01:013'],
['SR03:006', 'โมดูลจัดซื้อ: ใบขอซื้อ, ใบสั่งซื้อ, ใบแจ้งหนี้ซื้อ, ใบคืนสินค้าผู้ขาย (app/po/, PurchaseRequestManager, PurchaseOrderManager, SupplierReturnManager)', 'CR01:014'],
['SR03:007', 'โมดูลการเงิน: ใบวางบิลรับ, ใบเสร็จ, ใบวางบิลจ่าย, ใบสำคัญจ่าย (app/finance/, ReceiptBillingManager, ReceiptManager, PaymentBillingManager, PaymentManager)', 'CR01:015'],
['SR03:008', 'โมดูลบัญชี: ผังบัญชี, แผนก, สูตรบัญชี, สมุดรายวัน, บัญชีแยกประเภท (app/accounting/, app/journal/, PostingManager)', 'CR01:016'],
['SR03:009', 'โมดูลรายงานและแดชบอร์ด: รายงานสต๊อก, รายงานการเงิน, แดชบอร์ด (app/reports/, app/dashboard/, app/ac_dashboard/, ReportManager, EtlStockManager)', 'CR01:011, CR01:017, CR01:020'],
['SR03:010', 'โมดูลควบคุมเอกสาร: ออกเลขที่เอกสาร, วงจรชีวิต/สถานะ, ประวัติรายการ (DocumentNumberManager, BatchActionManager)', 'CR01:018, CR01:023'],
['SR03:011', 'บริการแจ้งเตือนและงานตามกำหนดเวลา: Socket.IO และ Scheduler (nodejs/server.js, nodejs/scheduler.js, app/cron/)', 'CR01:021, CR01:022'],
]],
['SR04', 'Software Elements Relationship', [
['SR04:001', 'ทุกโมดูลปฏิบัติการต้องเข้าถึงได้ผ่านการยืนยันตัวตนและการตรวจสิทธิ์ของโมดูลผู้ใช้งานเท่านั้น', 'CR01:002, CR01:024, CR04:003, CR05:002'],
['SR04:002', 'โมดูลขาย จัดซื้อ และการเงิน ต้องบันทึกผลกระทบต่อสต๊อกและ GL ผ่านโมดูลคลังสินค้าและบัญชี ไม่ทำซ้ำตรรกะ', 'CR01:013–CR01:016'],
['SR04:003', 'โมดูลรายงานต้องอ่านข้อมูลภายใต้ขอบเขตบริษัท/คลังที่โมดูลข้อมูลหลักและคลังสินค้าบังคับใช้', 'CR01:024, CR08:004'],
['SR04:004', 'บริการ Node.js ต้องรับเหตุการณ์จากแอปพลิเคชัน PHP ผ่าน Endpoint ภายในที่ป้องกันด้วย Secret เท่านั้น', 'CR01:021, CR03:003'],
['SR04:005', 'ทุกโมดูลที่สร้างเอกสารธุรกิจต้องเรียกใช้โมดูลควบคุมเอกสารเพื่อออกเลขที่เอกสาร', 'CR01:018'],
]],
['SR05', 'Performance Characteristics', [
['SR05:001', 'งานประจำวัน (เปิดหน้าจอ, ค้นหา, สร้างเอกสาร) ต้องเสร็จภายในเวลาที่ใช้งานได้จริงบนสภาพแวดล้อมที่ตกลง', 'CR02:001'],
['SR05:002', 'ยอดสรุปสต๊อก/GL/แดชบอร์ดต้องคำนวณล่วงหน้าตามกำหนดเวลา (etl_stock_summary, etl_gl_summary)', 'CR02:002, CR01:022, CR08:005'],
['SR05:003', 'ระบบต้องรองรับปริมาณข้อมูลและผู้ใช้พร้อมกันตามที่ตกลงสำหรับสภาพแวดล้อมใช้งานจริง', 'CR02:003'],
['SR05:004', 'ระบบต้องมี Health Check ทุก 5 นาที และแจ้งเตือนผู้ดูแลเมื่อล้มเหลวติดต่อกัน 2 ครั้ง', 'CR08:003'],
]],
['SR06', 'Software Interfaces', [
['SR06:001', 'แอปพลิเคชัน PHP ต้องเชื่อมต่อฐานข้อมูล MariaDB 2 ฐาน: wms (ผู้ใช้/บริษัท) และ wms2 (คลัง/บัญชี)', 'CR03:001'],
['SR06:002', 'ระบบต้องใช้งานได้บน Chrome, Edge และ Firefox รุ่นปัจจุบัน', 'CR03:002'],
['SR06:003', 'ระบบต้องรองรับ Mobile/Tablet Browser ผ่าน Responsive UI', 'CR07:003'],
['SR06:004', 'แอปพลิเคชัน PHP ต้องส่งเหตุการณ์ไป Node.js ผ่าน HTTP Endpoint ภายใน (NODE_EMIT_URL + NODE_EMIT_SECRET) และส่งอีเมลผ่าน SMTP ต่อบริษัท', 'CR03:003, CR03:005'],
['SR06:005', 'Browser ต้องเชื่อมต่อ Socket.IO Endpoint (NODE_PUBLIC_URL) เพื่อรับการแจ้งเตือน', 'CR03:004'],
]],
['SR07', 'Security Characteristics', [
['SR07:001', 'การเชื่อมต่อในสภาพแวดล้อมใช้งานจริงต้องผ่าน HTTPS/TLS', 'CR06:001'],
['SR07:002', 'การทำงานฝั่งเซิร์ฟเวอร์ต้องตรวจสอบข้อมูลนำเข้า ยืนยันตัวตน ตรวจสิทธิ์ และจำกัดขอบเขตบริษัท/คลัง', 'CR06:003, CR14:004'],
['SR07:003', 'ระบบต้องอนุญาต Session ที่ใช้งานอยู่เพียงหนึ่งต่อบัญชี (บล็อก Concurrent Login)', 'CR06:006'],
['SR07:004', 'ระบบต้องป้องกัน SQL Injection และ Cross-Site Scripting ทั้งด้านรับข้อมูลและแสดงผล', 'CR06:005'],
['SR07:005', 'RBAC (Owner/Admin/Staff/Viewer) ต้องบังคับใช้ที่ฝั่งเซิร์ฟเวอร์ ไม่ใช่เพียงการซ่อนเมนู', 'CR06:004'],
]],
['SR08', 'Database Design Requirements', [
['SR08:001', 'ฐานข้อมูล wms: user, company_list, company_map_user, company_setting, company_smtp, company_usage, whitelist', 'CR01:001–CR01:004, CR04:002'],
['SR08:002', 'ตารางข้อมูลหลัก (wms2): md_warehouse, md_storage, md_bin, md_product, md_product_category, md_contact, md_contact_type, md_account, md_account_formula, md_account_formula_item, md_department, md_barcode, md_sku_barcode_label, md_lot, md_lock_operation', 'CR01:005, CR01:010, CR01:016'],
['SR08:003', 'ตารางรายการ (td_*): td_stock, td_order/_item, td_quotation/_item, td_invoice/_item, td_return/_item, td_purchase_request/_item, td_purchase_order/_item, td_supplier_return/_item, td_receipt/_item, td_receipt_billing/_item, td_payment/_item, td_payment_billing/_item, td_gl/td_gl_item, td_batch_action, td_bin_log', 'CR01:007–CR01:018'],
['SR08:004', 'ตารางสรุปยอดและควบคุมเอกสาร: etl_stock_summary, etl_gl_summary, document_number_sequences, document_types, schema_migrations', 'CR01:018, CR01:022, CR09:004'],
['SR08:005', 'ตารางทั้งหมดต้องสร้างซ้ำได้ในสภาพแวดล้อมใหม่ผ่าน setup.php โดยไม่ต้องแก้ Schema ด้วยมือ', 'CR10:001, CR09:004'],
]],
['SR09', 'Error Handling and Recovery Attributes', [
['SR09:001', 'การลบ ยกเลิก หรือ Void เอกสารควบคุมต้องมี Confirm Dialog', 'CR05:004, CR01:023'],
['SR09:002', 'ข้อผิดพลาดต้องแสดงข้อความที่ผู้ใช้เข้าใจได้แทน Fatal Error', 'CR05:003, CR13:004'],
['SR09:003', 'การเปลี่ยนแปลงที่เกี่ยวข้องกัน (เอกสาร + สต๊อก + GL) ต้องเป็น Transaction และป้องกันยอดติดลบ/ซ้ำ', 'CR13:001, CR13:002, CR01:008'],
]],
];
export const SRS = SR_GROUPS.flatMap(([, , rows]) => rows);
export const srTopic = (id) => SRS.find((r) => r[0] === id)?.[1] ?? '';
/** Software Units grouped by module: [moduleId, moduleName, [[unitId, description, functional interface detail, srRefs, files]]]. */
export const UNIT_GROUPS = [
['UN01', 'ผู้ใช้งานและสิทธิ์ (Identity & Access)', [
['UN01.001', 'Identity – Register & Onboarding', 'ลงทะเบียนเจ้าของบริษัท, ยืนยันอีเมล, เชิญผู้ใช้และรับคำเชิญเข้าบริษัทที่ถูกต้อง', 'SR03:001, SR08:001', 'app/login/, UserManager.php'],
['UN01.002', 'Identity – Login & Role Guard', 'เข้าสู่ระบบด้วย Username/Password, ตรวจบทบาทและสิทธิ์แอปพลิเคชันฝั่งเซิร์ฟเวอร์ก่อนเข้าทุกหน้า', 'SR03:001, SR04:001, SR07:005', 'app/login/, UserManager.php, app/assets/utils/'],
['UN01.003', 'Identity – Password Recovery / OTP / Session', 'ลืมรหัสผ่านผ่านอีเมล, OTP, Hash รหัสผ่าน bcrypt, บล็อก Concurrent Login', 'SR03:001, SR01:004, SR07:003', 'PasswordManager.php, PasswordResetManager.php'],
['UN01.004', 'Identity – User & App-Access Administration', 'เพิ่ม/แก้ไข/ปิดใช้ผู้ใช้, กำหนดบทบาทและสิทธิ์การเข้าถึงแอปพลิเคชัน', 'SR03:001, SR08:001', 'app/setting/, UserManager.php'],
]],
['UN02', 'ตั้งค่าบริษัท (Company Settings)', [
['UN02.001', 'Company – Profile & System Settings', 'แก้ไขข้อมูลบริษัท, โลโก้, ค่าตั้งค่าระบบต่อบริษัท', 'SR03:002', 'CompanyProfileManager.php, CompanySettingManager.php'],
['UN02.002', 'Company – SMTP & Mail Dispatch', 'ตั้งค่า SMTP ต่อบริษัท และส่งอีเมล Onboarding/กู้คืนรหัสผ่าน/แจ้งเตือน', 'SR03:002, SR06:004', 'SmtpManager.php'],
]],
['UN03', 'ข้อมูลหลัก (Master Data)', [
['UN03.001', 'Master – Warehouse / Storage / Bin', 'จัดการคลังสินค้า พื้นที่ และช่องจัดเก็บ พร้อมความจุ', 'SR03:003, SR08:002', 'app/inventory/, WarehouseManager.php'],
['UN03.002', 'Master – Product & Category', 'จัดการหมวดสินค้า สินค้า หน่วยนับ และรูปสินค้า', 'SR03:003, SR08:002', 'ProductManager.php'],
['UN03.003', 'Master – Contact', 'จัดการประเภทผู้ติดต่อ ลูกค้า และผู้ขาย', 'SR03:003, SR08:002', 'app/contact/, ContactManager.php'],
['UN03.004', 'Master – Warehouse Layer Configuration', 'สลับโครงสร้างคลังแบบชั้นเดียว/หลายชั้นต่อบริษัท', 'SR03:003', 'CompanySettingManager.php, WarehouseManager.php'],
]],
['UN04', 'ควบคุมสินค้าคงคลัง (Inventory Control)', [
['UN04.001', 'ICS – Stock-in', 'รับสินค้าเข้าตามสินค้า จำนวน ตำแหน่ง เอกสารอ้างอิง สร้างความเคลื่อนไหวและยอดคงเหลือ', 'SR03:004, SR08:003', 'app/ics/, StockManager.php, StockTablesTrait.php'],
['UN04.002', 'ICS – Stock-out', 'จ่ายสินค้าออกโดยตรวจสิทธิ์และยอดคงเหลือ ปฏิเสธจำนวนเกิน', 'SR03:004, SR09:003', 'StockManager.php'],
['UN04.003', 'ICS – Stock Transfer', 'โอนย้ายระหว่างตำแหน่ง ต้นทาง/ปลายทางผูกเป็นรายการเดียว', 'SR03:004', 'StockManager.php'],
['UN04.004', 'ICS – Lot / Serial / Expiry', 'บันทึกและสอบกลับ Lot, Serial Number, วันหมดอายุ', 'SR03:004, SR08:002', 'StockSourceManager.php, md_lot'],
['UN04.005', 'ICS – Barcode Label & Scan', 'พิมพ์ฉลากบาร์โค้ด SKU/ตำแหน่ง และรับค่าจากเครื่องสแกน', 'SR03:004', 'BarcodeManager.php, location_barcode_label.php'],
['UN04.006', 'ICS – File Attachment', 'แนบและเรียกดูไฟล์ที่อนุญาตในรายการที่รองรับ', 'SR03:004', 'FileUploader.php'],
]],
['UN05', 'ขาย (Sales)', [
['UN05.001', 'Sales – Quotation', 'สร้าง/แก้ไข/อนุมัติใบเสนอราคา และแปลงเป็นใบสั่งขาย', 'SR03:005, SR04:002', 'app/order/, QuotationManager.php'],
['UN05.002', 'Sales – Sales Order', 'สร้างใบสั่งขาย ยืนยัน และตัดสต๊อกตามสถานะ', 'SR03:005, SR04:002', 'OrderManager.php'],
['UN05.003', 'Sales – Invoice', 'ออกใบแจ้งหนี้จากใบสั่งขาย และบันทึก GL', 'SR03:005, SR04:002', 'app/revenue/, InvoiceManager.php'],
['UN05.004', 'Sales – Return / Credit Note', 'รับคืนสินค้าและออกใบลดหนี้ พร้อมปรับสต๊อก/บัญชี', 'SR03:005, SR04:002', 'ReturnManager.php'],
]],
['UN06', 'จัดซื้อ (Purchasing)', [
['UN06.001', 'Purchasing – Purchase Request', 'สร้างและอนุมัติใบขอซื้อ', 'SR03:006', 'app/po/, PurchaseRequestManager.php'],
['UN06.002', 'Purchasing – Purchase Order', 'แปลงใบขอซื้อเป็นใบสั่งซื้อ และติดตามสถานะ', 'SR03:006, SR04:002', 'PurchaseOrderManager.php'],
['UN06.003', 'Purchasing – Purchase Invoice', 'บันทึกใบแจ้งหนี้ซื้อ รับสินค้าเข้า และบันทึก GL', 'SR03:006, SR04:002', 'PurchaseOrderManager.php, StockManager.php'],
['UN06.004', 'Purchasing – Supplier Return', 'คืนสินค้าผู้ขาย พร้อมปรับสต๊อก/บัญชี', 'SR03:006, SR04:002', 'SupplierReturnManager.php'],
]],
['UN07', 'การเงิน (Finance)', [
['UN07.001', 'Finance – Receipt Billing & Receipt', 'วางบิลรับและบันทึกใบเสรจรับเงินผูกกับใบแจ้งหนี้', 'SR03:007, SR04:002', 'app/finance/, ReceiptBillingManager.php, ReceiptManager.php'],
['UN07.002', 'Finance – Payment Billing & Payment', 'วางบิลจ่ายและบันทึกใบสำคัญจ่ายผูกกับใบแจ้งหนี้ซื้อ', 'SR03:007, SR04:002', 'PaymentBillingManager.php, PaymentManager.php'],
]],
['UN08', 'บัญชี (Accounting)', [
['UN08.001', 'Accounting – Chart of Accounts / Departments / Formulas', 'จัดการผังบัญชี แผนก และสูตรบัญชีอัตโนมัติ', 'SR03:008, SR08:002', 'app/accounting/, PostingManager.php'],
['UN08.002', 'Accounting – Journal & GL Posting', 'บันทึกสมุดรายวันและผ่านรายการ GL แบบสมดุลภายใน Transaction', 'SR03:008, SR09:003', 'app/journal/, PostingManager.php'],
]],
['UN09', 'รายงานและแดชบอร์ด (Reports & Dashboard)', [
['UN09.001', 'Reports – Dashboard & Aggregates', 'แดชบอร์ดคลังสินค้าและบัญชีจากตารางสรุปยอด', 'SR03:009, SR05:001, SR05:002', 'app/dashboard/, app/ac_dashboard/, EtlStockManager.php'],
['UN09.002', 'Reports – Stock Reports', 'ภาพรวมสต๊อก, ความเคลื่อนไหว, ความจุ, สินค้าใกล้หมด, หมดอายุ, Lot', 'SR03:009', 'app/reports/, ReportManager.php'],
['UN09.003', 'Reports – Financial Reports', 'งบทดลอง, งบกำไรขาดทุน, งบดุล, VAT, สมุดรายวัน, ความเคลื่อนไหว GL', 'SR03:009', 'ReportManager.php'],
['UN09.004', 'Reports – Filter / Print / Export', 'กรอง ดู พิมพ์ และส่งออกรายงานตามขอบเขตสิทธิ์', 'SR03:009, SR04:003', 'app/reports/'],
]],
['UN10', 'ควบคุมเอกสาร (Document Control)', [
['UN10.001', 'Document – Numbering', 'ออกเลขที่เอกสารตามลำดับที่กำหนด (document_number_sequences)', 'SR03:010, SR04:005, SR08:004', 'DocumentNumberManager.php'],
['UN10.002', 'Document – Lifecycle & Status', 'ควบคุมสถานะเอกสาร ปฏิเสธการเปลี่ยนสถานะที่ไม่ถูกต้อง พร้อม Confirm Dialog และข้อความข้อผิดพลาด', 'SR03:010, SR09:001, SR09:002', 'BatchActionManager.php, Manager classes'],
['UN10.003', 'Document – Audit Fields & History', 'บันทึกผู้สร้าง ผู้แก้ไข วันที่ และประวัติรายการ', 'SR03:010, SR09:001', 'td_* tables, td_bin_log'],
]],
['UN11', 'แจ้งเตือนและงานตามกำหนดเวลา (Notification & Scheduler)', [
['UN11.001', 'Node – Socket.IO Notification Server', 'รับเหตุการณ์จาก PHP ผ่าน Endpoint ที่ป้องกันด้วย Secret และส่งต่อไปยัง Browser ของผู้ใช้ที่เกี่ยวข้อง', 'SR03:011, SR04:004, SR06:004, SR06:005', 'nodejs/server.js'],
['UN11.002', 'Node – Scheduler (ETL & Alerts)', 'สรุปยอดสต๊อก/GL, แจ้งเตือนสินค้าใกล้หมดและใบแจ้งหนี้ค้างชำระ ตามกำหนดเวลา Asia/Bangkok', 'SR03:011, SR05:002, SR01:006', 'nodejs/scheduler.js, app/cron/'],
]],
['UN12', 'ความปลอดภัยและการแยกข้อมูล (Security & Tenant Scope)', [
['UN12.001', 'Security – Tenant Scope Guard', 'จำกัดทุก Query และ Action ให้อยู่ในบริษัท/คลังที่ผู้ใช้ได้รับอนุญาต', 'SR04:001, SR04:003, SR07:002', 'StockTablesTrait.php, Manager classes'],
['UN12.002', 'Security – Server-side Validation', 'ตรวจสอบข้อมูลนำเข้า, Prepared Statements, Escape Output ป้องกัน SQLi/XSS', 'SR07:002, SR07:004', 'Manager classes, app/assets/utils/'],
['UN12.003', 'Security – Operation Lock & Usage Guard', 'ล็อกช่วงเวลาผ่านรายการ และจำกัดโควตารายการต่อบริษัท', 'SR07:002, SR09:003', 'OperationLockManager.php, UsageGuard.php'],
['UN12.004', 'Security – TLS & Secret Configuration', 'ค่าตั้งค่า TLS, NODE_EMIT_SECRET และ Secrets ที่สร้างตอนติดตั้ง', 'SR07:001, SR01:005', 'docker/php/config.php.template, .env'],
]],
['UN13', 'ติดตั้งและสำรองข้อมูล (Deployment & Backup)', [
['UN13.001', 'Deploy – setup.php Schema Installer', 'สร้างฐานข้อมูล wms/wms2 และตารางทั้งหมดในขั้นตอนเดียว', 'SR01:003, SR08:005, SR06:001', 'setup.php, docker/mariadb/init-wms2.sql'],
['UN13.002', 'Deploy – Docker Compose Stack', 'Container php-apache, mariadb, node/pm2 พร้อม Entrypoint สร้าง config.php', 'SR02:005, SR01:003', 'docker-compose.yml, docker/'],
['UN13.003', 'Deploy – .env Generator', 'สร้าง .env และความลับอัตโนมัติ ไม่เก็บใน Git', 'SR01:005', 'docker/init-env.sh, .env.example'],
['UN13.004', 'Deploy – Backup & Restore', 'สำรอง Git 2 Remote, mysqldump รายวัน, ขั้นตอนกู้คืน', 'SR01:003', 'Product Operation Guide, backup remote'],
]],
];
export const UNITS = UNIT_GROUPS.flatMap(([, , rows]) => rows);
export const unitTopic = (id) => UNITS.find((u) => u[0] === id)?.[1] ?? '';
export const unitModule = (id) => UNIT_GROUPS.find(([m]) => id.startsWith(m))?.[1] ?? '';
/**
* Test cases: one per software unit. [tcId, unitId, input, output, environment, special, dependency]
*/
const ENV_WEB = 'Web Browser (Chrome/Edge/Firefox), PHP 8, MariaDB, Internal Testing Server';
const ENV_NODE = 'Node.js + pm2, Internal Testing Server';
export const TEST_CASES = [
['TC-UN01.001', 'UN01.001', 'ลงทะเบียนเจ้าของบริษัทใหม่ → ยืนยันอีเมล → เชิญผู้ใช้ → ผู้ใช้กดลิงก์เชิญ', 'สร้างบริษัท/บัญชีเจ้าของสำเร็จ ผู้ใช้ที่ถูกเชิญเข้าบริษัทที่ถูกต้อง', ENV_WEB + ', SMTP ทดสอบ', 'ต้องส่งอีเมลได้จริง', '-'],
['TC-UN01.002', 'UN01.002', 'เข้าสู่ระบบด้วยบัญชี Owner/Admin/Staff/Viewer แล้วเรียกหน้าและ Action ที่ไม่ได้รับสิทธิ์', 'แต่ละบทบาทเข้าถึงได้เฉพาะหน้าจอ/คำสั่งที่อนุญาต คำสั่งที่ไม่ได้รับสิทธิ์ถูกปฏิเสธฝั่งเซิร์ฟเวอร์', ENV_WEB, 'ต้องมีบัญชีทดสอบครบ 4 บทบาท', 'TC-UN01.001'],
['TC-UN01.003', 'UN01.003', 'ขอรีเซ็ตรหัสผ่าน → กรอก OTP → ตั้งรหัสใหม่; เข้าสู่ระบบบัญชีเดิมจาก 2 อุปกรณ์', 'รีเซ็ตสำเร็จโดยไม่เปิดเผยรหัสผ่านเดิม Session ที่สองถูกปฏิเสธ', ENV_WEB + ', SMTP ทดสอบ', '-', 'TC-UN01.002'],
['TC-UN01.004', 'UN01.004', 'Admin เพิ่มผู้ใช้ เปลี่ยนบทบาท และปิดสิทธิ์แอปพลิเคชัน; Staff ลองทำรายการเดียวกัน', 'การเปลี่ยนแปลงของ Admin ถูกบันทึก Staff ถูกปฏิเสธ', ENV_WEB, '-', 'TC-UN01.002'],
['TC-UN02.001', 'UN02.001', 'แก้ไขข้อมูลบริษัทและค่าตั้งค่าระบบ', 'ค่าใหม่ถูกบันทึกและมีผลกับบริษัทนั้นเท่านั้น', ENV_WEB, '-', 'TC-UN01.002'],
['TC-UN02.002', 'UN02.002', 'ตั้งค่า SMTP แล้วส่งอีเมลทดสอบ/คำเชิญ', 'อีเมลถูกส่งผ่าน SMTP ของบริษัทและได้รับที่ปลายทาง', ENV_WEB + ', SMTP ทดสอบ', 'ต้องมีบัญชี SMTP ทดสอบ', 'TC-UN02.001'],
['TC-UN03.001', 'UN03.001', 'เพิ่ม/แก้ไข/ปิดใช้ คลังสินค้า พื้นที่ และช่องจัดเก็บ; กรอกความจุไม่ถูกต้อง', 'รายการถูกต้องถูกบันทึก ข้อมูลผิดถูกปฏิเสธพร้อมข้อความ', ENV_WEB, '-', 'TC-UN01.002'],
['TC-UN03.002', 'UN03.002', 'เพิ่มหมวดสินค้าและสินค้าพร้อมหน่วยนับ', 'สินค้าแสดงในรายการและเลือกใช้ในรายการสต๊อกได้', ENV_WEB, '-', 'TC-UN01.002'],
['TC-UN03.003', 'UN03.003', 'เพิ่มประเภทผู้ติดต่อ ลูกค้า และผู้ขาย', 'ผู้ติดต่อเลือกใช้ในเอกสารขาย/ซื้อได้', ENV_WEB, '-', 'TC-UN01.002'],
['TC-UN03.004', 'UN03.004', 'ตั้งบริษัท A เป็นคลังชั้นเดียว บริษัท B เป็นคลัง/พื้นที่/ช่อง แล้วรับสินค้าเข้า', 'ทั้งสองบริษัททำรายการได้ถูกต้องตามโครงสร้างของตน', ENV_WEB + ', 2 บริษัททดสอบ', '-', 'TC-UN03.001'],
['TC-UN04.001', 'UN04.001', 'รับสินค้าเข้า ระบุสินค้า จำนวน ตำแหน่ง เอกสารอ้างอิง; กรอกจำนวนติดลบ', 'สร้างความเคลื่อนไหวและยอดคงเหลือถูกต้อง จำนวนติดลบถูกปฏิเสธ', ENV_WEB, '-', 'TC-UN03.002'],
['TC-UN04.002', 'UN04.002', 'จ่ายสินค้าออกภายในยอดคงเหลือ และจ่ายเกินยอด', 'ยอดลดลงถูกต้อง การจ่ายเกินยอดถูกปฏิเสธ', ENV_WEB, '-', 'TC-UN04.001'],
['TC-UN04.003', 'UN04.003', 'โอนย้ายระหว่าง 2 ตำแหน่งที่ได้รับอนุญาต', 'ต้นทางลด ปลายทางเพิ่ม ผูกเป็นรายการโอนเดียวและสอบกลับได้', ENV_WEB, '-', 'TC-UN04.001'],
['TC-UN04.004', 'UN04.004', 'รับสินค้าเข้าพร้อม Lot, Serial Number, วันหมดอายุ แล้วเปิดรายงาน Lot/หมดอายุ', 'ข้อมูลติดตามคงอยู่และแสดงในรายงานที่เกี่ยวข้อง', ENV_WEB, '-', 'TC-UN04.001'],
['TC-UN04.005', 'UN04.005', 'พิมพ์ฉลาก SKU/ตำแหน่ง แล้วสแกน (หรือป้อนค่า) ในหน้าจอรับเข้า', 'ฉลากมีรหัสที่ใช้งานได้ ค่าที่สแกนถูกยอมรับ', ENV_WEB + ', เครื่องสแกนหรือจำลอง', 'เครื่องพิมพ์/สแกน (ถ้ามี)', 'TC-UN03.002'],
['TC-UN04.006', 'UN04.006', 'แนบไฟล์ที่อนุญาตและไฟล์ที่ไม่อนุญาตกับรายการ; ผู้ใช้บริษัทอื่นเรียกดู', 'ไฟล์ที่อนุญาตอัปโหลด/เรียกดูได้เฉพาะผู้มีสิทธิ์ ไฟล์ที่ไม่อนุญาตถูกปฏิเสธ', ENV_WEB, 'เตรียมไฟล์ตัวอย่าง', 'TC-UN03.002'],
['TC-UN05.001', 'UN05.001', 'สร้างใบเสนอราคาให้ลูกค้าและแปลงเป็นใบสั่งขาย', 'ใบสั่งขายถูกสร้างพร้อมอ้างอิงใบเสนอราคา', ENV_WEB, '-', 'TC-UN03.003'],
['TC-UN05.002', 'UN05.002', 'ยืนยันใบสั่งขายและตรวจสต๊อก', 'สถานะเปลี่ยนตามที่อนุญาตและตัดสต๊อกถูกต้อง', ENV_WEB, '-', 'TC-UN05.001, TC-UN04.001'],
['TC-UN05.003', 'UN05.003', 'ออกใบแจ้งหนี้จากใบสั่งขาย', 'ใบแจ้งหนี้ได้เลขที่เอกสารและบันทึก GL สมดุล', ENV_WEB, '-', 'TC-UN05.002'],
['TC-UN05.004', 'UN05.004', 'รับคืนสินค้าและออกใบลดหนี้', 'สต๊อกเพิ่มกลับและบัญชีปรับตามใบลดหนี้', ENV_WEB, '-', 'TC-UN05.003'],
['TC-UN06.001', 'UN06.001', 'สร้างและอนุมัติใบขอซื้อ', 'ใบขอซื้ออยู่ในสถานะอนุมัติ', ENV_WEB, '-', 'TC-UN03.003'],
['TC-UN06.002', 'UN06.002', 'แปลงใบขอซื้อเป็นใบสั่งซื้อ', 'ใบสั่งซื้อถูกสร้างพร้อมอ้างอิงใบขอซื้อ', ENV_WEB, '-', 'TC-UN06.001'],
['TC-UN06.003', 'UN06.003', 'บันทึกใบแจ้งหนี้ซื้อและรับสินค้าเข้าคลัง', 'สต๊อกเพิ่มและ GL บันทึกสมดุล', ENV_WEB, '-', 'TC-UN06.002'],
['TC-UN06.004', 'UN06.004', 'คืนสินค้าให้ผู้ขาย', 'สต๊อกลดและบัญชีปรับตามใบคืน', ENV_WEB, '-', 'TC-UN06.003'],
['TC-UN07.001', 'UN07.001', 'วางบิลรับและบันทึกใบเสร็จรับเงินกับใบแจ้งหนี้', 'ยอดค้างชำระลดลง เอกสารเชื่อมโยงกันและมีประวัติ', ENV_WEB, '-', 'TC-UN05.003'],
['TC-UN07.002', 'UN07.002', 'วางบิลจ่ายและบันทึกใบสำคัญจ่ายกับใบแจ้งหนี้ซื้อ', 'ยอดค้างจ่ายลดลง เอกสารเชื่อมโยงกันและมีประวัติ', ENV_WEB, '-', 'TC-UN06.003'],
['TC-UN08.001', 'UN08.001', 'เพิ่มผังบัญชี แผนก และสูตรบัญชี', 'โครงสร้างบัญชีถูกบันทึกและใช้กับการผ่านรายการได้', ENV_WEB, '-', 'TC-UN01.004'],
['TC-UN08.002', 'UN08.002', 'บันทึกสมุดรายวันแบบสมดุลและแบบไม่สมดุล; จำลองความล้มเหลวระหว่างผ่านรายการ', 'รายการสมดุลถูกผ่าน GL รายการไม่สมดุลถูกปฏิเสธ ความล้มเหลวถูก Rollback ทั้งชุด', ENV_WEB, 'ต้องจำลองความล้มเหลวบน Testing Server', 'TC-UN08.001'],
['TC-UN09.001', 'UN09.001', 'เปิดแดชบอร์ดคลังสินค้าและบัญชีหลังรันงานสรุปยอด', 'ตัวเลขตรงกับตารางสรุปยอดและแสดงผลภายในเวลาที่ใช้งานได้', ENV_WEB, '-', 'TC-UN11.002'],
['TC-UN09.002', 'UN09.002', 'เปิดรายงานภาพรวมสต๊อก ความเคลื่อนไหว ความจุ สินค้าใกล้หมด หมดอายุ และ Lot พร้อมตัวกรอง', 'รายงานแสดงข้อมูลที่ได้รับสิทธิ์ตามตัวกรอง', ENV_WEB, '-', 'TC-UN04.001–TC-UN04.004'],
['TC-UN09.003', 'UN09.003', 'เปิดงบทดลอง งบกำไรขาดทุน งบดุล VAT สมุดรายวัน และความเคลื่อนไหว GL', 'ยอดรวมสอดคล้องกันในงวดที่เลือก', ENV_WEB, '-', 'TC-UN08.002'],
['TC-UN09.004', 'UN09.004', 'กรอง ดู พิมพ์ และส่งออกรายงาน', 'ผลลัพธ์ตรงกับขอบเขตและตัวกรองที่เลือก', ENV_WEB, '-', 'TC-UN09.002'],
['TC-UN10.001', 'UN10.001', 'สร้างเอกสารควบคุมหลายฉบับติดต่อกัน', 'เลขที่เอกสารเรียงตามลำดับที่ตั้งค่า ไม่ซ้ำ', ENV_WEB, '-', 'TC-UN05.001'],
['TC-UN10.002', 'UN10.002', 'พยายามเปลี่ยนสถานะเอกสารข้ามขั้น; ลบเอกสารควบคุม', 'การเปลี่ยนสถานะที่ไม่ถูกต้องถูกปฏิเสธพร้อมข้อความ การลบต้องยืนยันก่อน', ENV_WEB, '-', 'TC-UN10.001'],
['TC-UN10.003', 'UN10.003', 'สร้างแล้วแก้ไขรายการ แล้วเปิดดูประวัติ', 'ผู้สร้าง ผู้แก้ไข วันที่ และสถานะถูกบันทึกครบ', ENV_WEB, '-', 'TC-UN03.002'],
['TC-UN11.001', 'UN11.001', 'เปลี่ยนสถานะเอกสารที่ต้องแจ้งเตือน; เรียก Endpoint แจ้งเตือนโดยไม่มี Secret', 'ผู้ใช้ที่เกี่ยวข้องได้รับแจ้งเตือน Real-time เท่านั้น การเรียกโดยไม่มี Secret ถูกปฏิเสธ', ENV_WEB + ', ' + ENV_NODE, 'บริการ Node.js ทำงานอยู่', 'TC-UN01.002'],
['TC-UN11.002', 'UN11.002', 'รันงานสรุปยอดสต๊อก/GL และแจ้งเตือนสินค้าใกล้หมด/ใบแจ้งหนี้ค้างชำระ; ตรวจเวลาบันทึกเทียบ Asia/Bangkok', 'งานเสร็จโดยไม่ซ้ำ ผลลัพธ์อยู่ในขอบเขตสิทธิ์ เวลาถูกต้องตามเขตเวลา', ENV_NODE, 'Scheduler ทำงานอยู่', 'TC-UN04.001, TC-UN07.001'],
['TC-UN12.001', 'UN12.001', 'ผู้ใช้บริษัท A เรียกข้อมูล/รายการของบริษัท B และคลังที่ไม่ได้รับสิทธิ์ ทั้งผ่าน UI และ URL/ API โดยตรง', 'ถูกปฏิเสธทุกช่องทาง ไม่มีข้อมูลรั่วไหลหรือถูกแก้ไข', ENV_WEB + ', 2 บริษัททดสอบ', '-', 'TC-UN01.002, TC-UN03.001'],
['TC-UN12.002', 'UN12.002', 'ส่งข้อมูลนำเข้าที่มี SQL/Script และค่าที่ไม่ถูกต้องไปยัง Action ฝั่งเซิร์ฟเวอร์', 'ถูกปฏิเสธหรือ Escape โดยไม่มีข้อมูลเปลี่ยนแปลงหรือ Script ทำงาน', ENV_WEB + ', เครื่องมือทดสอบ API', '-', 'TC-UN01.002'],
['TC-UN12.003', 'UN12.003', 'ผ่านรายการในงวดที่ล็อก; ทำรายการเกินโควตาบริษัท', 'รายการถูกปฏิเสธพร้อมข้อความ', ENV_WEB, '-', 'TC-UN08.002'],
['TC-UN12.004', 'UN12.004', 'ตรวจ Repository และสภาพแวดล้อมที่ติดตั้งว่ามีความลับหรือไฟล์ตั้งค่าเปิดเผยหรือไม่; เรียกผ่าน HTTPS', 'ไม่พบความลับใน Git หรือเข้าถึงได้จากเว็บ การเชื่อมต่อผ่าน TLS', 'Repository, Deployed Environment', '-', '-'],
['TC-UN13.001', 'UN13.001', 'ติดตั้งบนเครื่องใหม่ด้วย setup.php', 'ฐานข้อมูลและตารางครบตาม SR08 โดยไม่แก้ Schema ด้วยมือ', 'Fresh Linux Server, PHP 8, MariaDB', 'ต้องใช้เครื่องว่าง', '-'],
['TC-UN13.002', 'UN13.002', 'รัน docker compose up -d --build', 'Container php-apache, mariadb, node ทำงานและเข้าใช้ระบบได้', 'Docker Host', '-', 'TC-UN13.003'],
['TC-UN13.003', 'UN13.003', 'รัน docker/init-env.sh โดยเว้นค่าความลับว่าง', '.env ถูกสร้างพร้อมความลับสุ่ม และไม่ถูก Git ติดตาม', 'Docker Host', '-', '-'],
['TC-UN13.004', 'UN13.004', 'สำรองฐานข้อมูล กู้คืนสู่สภาพแวดล้อมทดสอบ และ Clone จาก Backup Remote', 'ข้อมูลและ Source Code กู้คืนได้ครบตามขั้นตอนในคู่มือ', 'Testing Server, Backup Remote', 'ตาม Product Operation Guide', 'TC-UN13.001'],
];
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. */
export const TRACE = {
'CR01:001': { sr: ['SR03:001', 'SR08:001'], un: ['UN01.001'] },
'CR01:002': { sr: ['SR03:001', 'SR04:001', 'SR07:005'], un: ['UN01.002'] },
'CR01:003': { sr: ['SR03:001', 'SR07:003'], un: ['UN01.003'] },
'CR01:004': { sr: ['SR03:001', 'SR03:002'], un: ['UN01.004', 'UN02.001', 'UN02.002'] },
'CR01:005': { sr: ['SR03:003', 'SR08:002'], un: ['UN03.001', 'UN03.002', 'UN03.003'] },
'CR01:006': { sr: ['SR03:003'], un: ['UN03.004'] },
'CR01:007': { sr: ['SR03:004', 'SR08:003'], un: ['UN04.001'] },
'CR01:008': { sr: ['SR03:004', 'SR09:003'], un: ['UN04.002'] },
'CR01:009': { sr: ['SR03:004'], un: ['UN04.003'] },
'CR01:010': { sr: ['SR03:004', 'SR08:002'], un: ['UN04.004'] },
'CR01:011': { sr: ['SR03:009'], un: ['UN09.002'] },
'CR01:012': { sr: ['SR03:004'], un: ['UN04.005'] },
'CR01:013': { sr: ['SR03:005', 'SR04:002'], un: ['UN05.001', 'UN05.002', 'UN05.003', 'UN05.004'] },
'CR01:014': { sr: ['SR03:006', 'SR04:002'], un: ['UN06.001', 'UN06.002', 'UN06.003', 'UN06.004'] },
'CR01:015': { sr: ['SR03:007', 'SR04:002'], un: ['UN07.001', 'UN07.002'] },
'CR01:016': { sr: ['SR03:008'], un: ['UN08.001', 'UN08.002'] },
'CR01:017': { sr: ['SR03:009'], un: ['UN09.003'] },
'CR01:018': { sr: ['SR03:010', 'SR04:005', 'SR08:004'], un: ['UN10.001', 'UN10.002'] },
'CR01:019': { sr: ['SR03:004'], un: ['UN04.006'] },
'CR01:020': { sr: ['SR03:009'], un: ['UN09.004'] },
'CR01:021': { sr: ['SR03:011', 'SR04:004', 'SR06:005'], un: ['UN11.001'] },
'CR01:022': { sr: ['SR03:011', 'SR05:002', 'SR08:004'], un: ['UN11.002'] },
'CR01:023': { sr: ['SR03:010', 'SR09:001'], un: ['UN10.003'] },
'CR01:024': { sr: ['SR04:001', 'SR04:003', 'SR07:002'], un: ['UN12.001'] },
'CR02:001': { sr: ['SR05:001'], un: ['UN09.001'] },
'CR02:002': { sr: ['SR05:002'], un: ['UN09.001', 'UN11.002'] },
'CR02:003': { sr: ['SR05:003'], un: ['UN09.001'] },
'CR03:001': { sr: ['SR06:001'], un: ['UN13.001'] },
'CR03:002': { sr: ['SR06:002'], un: ['UN01.002'] },
'CR03:003': { sr: ['SR06:004', 'SR04:004'], un: ['UN11.001'] },
'CR03:004': { sr: ['SR06:005'], un: ['UN11.001'] },
'CR03:005': { sr: ['SR06:004', 'SR03:002'], un: ['UN02.002'] },
'CR04:001': { sr: ['SR02:001'], un: ['UN01.002'] },
'CR04:002': { sr: ['SR08:001', 'SR08:002', 'SR08:003'], un: ['UN13.001'] },
'CR04:003': { sr: ['SR04:001'], un: ['UN01.002'] },
'CR04:004': { sr: ['SR01:002'], un: ['UN13.001'] },
'CR05:001': { sr: ['SR02:002'], un: ['UN01.002'] },
'CR05:002': { sr: ['SR04:001'], un: ['UN01.002'] },
'CR05:003': { sr: ['SR09:002'], un: ['UN10.002'] },
'CR05:004': { sr: ['SR09:001'], un: ['UN10.002'] },
'CR05:005': { sr: ['SR03:004'], un: ['UN04.005'] },
'CR06:001': { sr: ['SR07:001'], un: ['UN12.004'] },
'CR06:002': { sr: ['SR01:005'], un: ['UN13.003', 'UN12.004'] },
'CR06:003': { sr: ['SR07:002'], un: ['UN12.002'] },
'CR06:004': { sr: ['SR07:005'], un: ['UN01.002'] },
'CR06:005': { sr: ['SR07:004'], un: ['UN12.002'] },
'CR06:006': { sr: ['SR07:003'], un: ['UN01.003'] },
'CR07:001': { sr: ['SR01:003'], un: ['UN13.001', 'UN13.002'] },
'CR07:002': { sr: ['SR01:006'], un: ['UN11.002'] },
'CR07:003': { sr: ['SR06:003', 'SR02:002'], un: ['UN01.002'] },
'CR07:004': { sr: ['SR01:003'], un: ['UN13.001'] },
'CR08:001': { sr: ['SR01:003'], un: ['UN13.004'] },
'CR08:002': { sr: ['SR01:003'], un: ['UN13.004'] },
'CR08:003': { sr: ['SR05:004'], un: ['UN11.002'] },
'CR08:004': { sr: ['SR04:003'], un: ['UN12.001'] },
'CR08:005': { sr: ['SR05:002'], un: ['UN11.002'] },
'CR09:001': { sr: ['SR02:003', 'SR02:004'], un: ['UN13.001'] },
'CR09:002': { sr: ['SR01:005'], un: ['UN13.003'] },
'CR09:003': { sr: ['SR01:001'], un: ['UN13.004'] },
'CR09:004': { sr: ['SR08:005', 'SR08:004'], un: ['UN13.001'] },
'CR10:001': { sr: ['SR01:003', 'SR08:005'], un: ['UN13.001'] },
'CR10:002': { sr: ['SR02:005'], un: ['UN13.002'] },
'CR10:003': { sr: ['SR01:005'], un: ['UN13.003'] },
'CR10:004': { sr: ['SR01:001'], un: ['UN13.001'] },
'CR11:001': { sr: ['SR01:001'], un: ['UN01.002'] },
'CR11:002': { sr: ['SR01:001'], un: ['UN13.001'] },
'CR11:003': { sr: ['SR01:001'], un: ['UN01.002'] },
'CR11:004': { sr: ['SR01:001'], un: ['UN13.004'] },
'CR12:001': { sr: ['SR01:002'], un: ['UN13.001'] },
'CR12:002': { sr: ['SR01:001'], un: ['UN13.001'] },
'CR12:003': { sr: ['SR01:004'], un: ['UN01.003'] },
'CR12:004': { sr: ['SR01:001'], un: ['UN13.001'] },
'CR13:001': { sr: ['SR09:003'], un: ['UN08.002'] },
'CR13:002': { sr: ['SR09:003'], un: ['UN04.002'] },
'CR13:003': { sr: ['SR01:003'], un: ['UN13.004'] },
'CR13:004': { sr: ['SR09:002'], un: ['UN10.002'] },
'CR14:001': { sr: ['SR01:001'], un: ['UN01.002'] },
'CR14:002': { sr: ['SR01:001'], un: ['UN01.002'] },
'CR14:003': { sr: ['SR01:001'], un: ['UN01.002'] },
'CR14:004': { sr: ['SR07:002'], un: ['UN12.001'] },
};
export const traceOf = (crId) => {
const t = TRACE[crId];
if (!t) throw new Error(`No traceability entry for ${crId}`);
return { ...t, tc: tcByUnit(t.un[0]) };
};
// Sanity: every CR is traced and every trace target exists.
for (const [id] of CRS) {
const t = traceOf(id);
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}`);
if (!t.tc) throw new Error(`${id} has no test case`);
}
+25
View File
@@ -0,0 +1,25 @@
// Document scaffold shared by every generated work product.
import { CODE, PROJECT_TH } from './project.mjs';
import { control, signatures, doc } from './lib.mjs';
/**
* Build a work product in the audited package layout:
* title → footer marker → document-control block → body sections → three signature tables.
*/
export function workProduct({ type, footer, rows, body, secretary, reviewer, approver }) {
return doc(
`# ${type}`,
`<!-- footer: ${footer} -->`,
control(rows),
body,
signatures(secretary, reviewer, approver),
);
}
/** The Document No / Project Name / Project Code rows every work product starts with. */
export const head = (type, release, extra = []) => [
['Document No', type, 'Release, Version, By:', release],
['Project Name', PROJECT_TH],
['Project Code', CODE],
...extra,
];
+256
View File
@@ -0,0 +1,256 @@
// Project history: work schedule, weekly progress periods, meetings, corrections and changes.
// Every dated row is anchored to the controlled Git history of the delivered application.
import { P, CRS } from './project.mjs';
/** Work Schedule: [no, phase, task, detail, responsible(ids), start, finish, deliverable, status, remark] */
export const TASKS = [
['1.1', 'Project Initiation', 'Kick-off Meeting', 'เปิดโครงการ ชี้แจงขอบเขต เป้าหมาย และทีมงาน', ['ApS', 'SeV'], '2026-01-05', '2026-01-09', 'Statement of Work', 'Completed', 'เปิดโครงการอย่างเป็นทางการ'],
['1.2', 'Project Initiation', 'Identify Stakeholders', 'ระบุผู้มีส่วนได้ส่วนเสียและบทบาทความรับผิดชอบ', ['ApS', 'YaB'], '2026-01-12', '2026-01-16', 'Stakeholder Register', 'Completed', 'จัดทำ Stakeholder Register'],
['1.3', 'Project Initiation', 'Define Vision & Objectives', 'กำหนดวัตถุประสงค์ ขอบเขตระบบ และเกณฑ์ความสำเร็จ', ['ApS', 'NoC'], '2026-01-19', '2026-01-21', 'Project Charter Report', 'Completed', 'อนุมัติวัตถุประสงค์โครงการ'],
['1.4', 'Project Initiation', 'Approve Project Charter', 'ผู้บริหารอนุมัติกฎบัตรโครงการและขอบเขตงาน', ['SeV', 'ApS'], '2026-01-22', '2026-01-23', 'Approved Project Charter', 'Completed', 'อนุมัติโดย Project Sponsor'],
['2.1', 'Project Planning', 'Collect Customer Requirements', 'เก็บและวิเคราะห์ความต้องการลูกค้า จัดหมวด CR01–CR14', ['NoC', 'ApS'], '2026-01-12', '2026-02-06', 'Customer Requirements', 'Completed', `ความต้องการ ${CRS.length} รายการ ผลสรุป A ทั้งหมด`],
['2.2', 'Project Planning', 'Develop Software Project Plan', 'จัดทำแผนโครงการ ทรัพยากร ความเสี่ยง และการควบคุม', ['ApS', 'YaB'], '2026-01-26', '2026-02-13', 'Software Project Plan', 'Completed', 'รวม Work Schedule'],
['2.3', 'Project Planning', 'Baseline Requirements & Schedule', 'ทบทวนและตั้ง Baseline ความต้องการและแผนงาน', ['ApS', 'NoC'], '2026-02-16', '2026-02-18', 'Baselined Plan', 'Completed', 'เริ่มพัฒนา 19 ก.พ. 69'],
['3.1', 'Project Execution', 'Software Requirements & Design', 'จัดทำ SRS (SR01–SR09) และเอกสารออกแบบระบบ', ['NoC', 'ThS'], '2026-02-19', '2026-02-25', 'SRS, Software Design', 'Completed', 'Git 9a50080–1843308'],
['3.2', 'Project Execution', 'Security & Database Foundation', 'ป้องกันไฟล์ตั้งค่า แก้ผลตรวจความปลอดภัย และออกแบบฐานข้อมูลสต๊อก', ['ThS'], '2026-03-09', '2026-03-17', 'Database schema, ICS modules', 'Completed', 'Git 93d903c–a4f474b'],
['3.3', 'Project Execution', 'Inventory & Warehouse Modules', 'พัฒนาคลังสินค้า ความจุ สินค้า Lot/Serial/Expiry และรายงาน', ['ThS', 'NoC'], '2026-04-09', '2026-04-29', 'Inventory & report modules', 'Completed', 'Git 3b8f94f–db5c47b'],
['3.4', 'Project Execution', 'Authentication & Onboarding', 'พัฒนาเข้าสู่ระบบ ลงทะเบียน Onboarding และสิทธิ์ตามบทบาท', ['ThS'], '2026-04-28', '2026-05-12', 'Authentication modules', 'Completed', 'Git d8fd30c–79e66bd'],
['3.5', 'Project Execution', 'Order & Barcode Workflows', 'พัฒนาใบสั่งขาย ใบรับคืน ใบแจ้งหนี้ คลังหลายชั้น และบาร์โค้ด', ['ThS'], '2026-05-02', '2026-05-08', 'Order & barcode modules', 'Completed', 'Git a90975d–304848d'],
['3.6', 'Project Execution', 'Production Preparation & Setup', 'เตรียมใช้งานจริง Base URL อัตโนมัติ และสคริปต์ติดตั้งฐานข้อมูล', ['ThS'], '2026-05-11', '2026-05-13', 'setup.php, configuration', 'Completed', 'Git d989e59–bf3be28'],
['3.7', 'Project Execution', 'Accounting & Finance Workflows', 'พัฒนาผังบัญชี GL สมุดรายวัน ใบวางบิล ใบเสร็จ และใบสำคัญจ่าย', ['ThS'], '2026-05-13', '2026-05-23', 'Accounting & finance modules', 'Completed', 'Git 04a683b–eddb10a'],
['3.8', 'Project Execution', 'Real-time Services & Scheduled Jobs', 'พัฒนา Socket.IO, ตารางสรุปยอด และงานแจ้งเตือนตามกำหนดเวลา', ['ThS'], '2026-05-22', '2026-05-27', 'Node.js services', 'Completed', 'Git 9c275eb–b8798bc'],
['3.9', 'Project Execution', 'Security Hardening & Lifecycle Review', 'ทบทวน Role Guard ขอบเขตบริษัท วงจรเอกสาร และโควตารายการ', ['ThS', 'PaNg'], '2026-05-21', '2026-05-28', 'Hardening commits', 'Completed', 'Git 6eeebfe–ed3dd2f'],
['3.10', 'Project Execution', 'Refactor & Development Baseline', 'ลดความซ้ำซ้อนของคลาสและตั้ง Baseline การพัฒนา', ['ThS'], '2026-05-29', '2026-05-29', 'Development baseline', 'Completed', 'Git a0677d6'],
['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.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.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.7', 'Stabilization', 'Demonstration Data & Delivery Package', 'เตรียมข้อมูลสาธิต ปรับแบรนด์ และชุดติดตั้ง Docker Compose', ['ThS'], '2026-08-14', '2026-08-17', 'Demo data, Docker stack', 'Completed', 'Git dd48a8b–6c39700'],
['5.1', 'Project Close', 'Final Work-product Review', 'ตรวจสอบความครบถ้วนของ Work Products และหลักฐาน', ['ApS', 'YaB'], '2026-08-10', '2026-08-17', 'List of Evidence', 'Completed', 'ตรวจรอบที่ 4 เมื่อ 17 ส.ค. 69'],
['5.2', 'Project Close', 'Training', 'อบรมผู้ใช้งานก่อนเปิดใช้งานจริง', ['ThS', 'PaNg'], '2026-08-22', '2026-08-22', 'Training Report', 'Completed', 'ผู้เข้าอบรม 6 คน'],
['5.3', 'Project Close', 'Acceptance & Project Closure', 'ส่งมอบระบบ ตรวจรับ และปิดโครงการ', ['SeV', 'ApS'], '2026-08-17', '2026-08-24', 'Acceptance Report', 'Completed', 'ตรวจรับ 17 ส.ค. 69 ปิดโครงการ 24 ส.ค. 69'],
];
export const taskById = (id) => TASKS.find((t) => t[0] === id);
/** Correction Register: [id, detected, corrected, severity, problem, cause, action, ownerId, commit, testCase] */
export const CORRECTIONS = [
['ISS-001', '2026-03-03', '2026-03-09', 'สูง', 'ผลตรวจความปลอดภัยเบื้องต้นพบจุดที่ต้องแก้ไข', 'ยังไม่ได้ใช้แนวปฏิบัติ Secure Coding ครบทุกจุด', 'แก้ไขตามผลตรวจความปลอดภัยทั้งชุด', 'ThS', '7cb78d0', 'TC-UN12.002'],
['ISS-002', '2026-04-18', '2026-04-24', 'สูง', 'คำสั่งบางรายการทำให้ความถูกต้องของข้อมูลเสียหาย', 'ไม่มีการตรวจสอบความสัมพันธ์ของข้อมูลก่อนทำรายการ', 'ถอดคำสั่งที่มีปัญหาและเพิ่มการตรวจสอบความถูกต้อง', 'ThS', '92d116f', 'TC-UN08.002'],
['ISS-003', '2026-04-30', '2026-05-06', 'สูง', 'การตรวจสิทธิ์ตามบทบาทยังไม่ครอบคลุมทุกหน้าจอ', 'พัฒนาสิทธิ์แบบเพิ่มทีละส่วนระหว่างพัฒนา', 'เพิ่ม Role Guard ให้ครบและปรับปรุงเอกสารประกอบ', 'ThS', '2eb6a1a', 'TC-UN01.002'],
['ISS-004', '2026-05-01', '2026-05-07', 'สูง', 'พบช่องโหว่ด้านความปลอดภัยที่ยังไม่ได้ปิด', 'การตรวจสอบสิทธิ์บางเส้นทางยังไม่สมบูรณ์', 'ปิดช่องโหว่ที่พบตามผลการตรวจสอบ', 'ThS', 'a75d37e', 'TC-UN12.002'],
['ISS-005', '2026-05-02', '2026-05-08', 'กลาง', 'ชื่อตัวแปร/ฟังก์ชันไม่สอดคล้องกัน และ Session ทำงานผิดพลาด', 'พัฒนาเป็นช่วง ๆ ทำให้การตั้งชื่อไม่เป็นมาตรฐานเดียวกัน', 'ปรับชื่อให้เป็นมาตรฐานเดียวกันและแก้ปัญหา Session', 'ThS', '304848d', 'TC-UN01.003'],
['ISS-006', '2026-05-05', '2026-05-11', 'สูง', 'พบประเด็นความปลอดภัยของเว็บแอปพลิเคชัน', 'การตรวจสอบข้อมูลนำเข้ายังไม่ครอบคลุม', 'แก้ไขความปลอดภัยของเว็บแอปพลิเคชันตามที่ตรวจพบ', 'ThS', '7a87909', 'TC-UN12.002'],
['ISS-007', '2026-05-06', '2026-05-12', 'กลาง', 'ขั้นตอน Onboarding ทำงานไม่ครบตามที่ออกแบบ', 'เงื่อนไขการรับคำเชิญไม่ครอบคลุมทุกกรณี', 'แก้ไขขั้นตอน Onboarding ให้ทำงานถูกต้อง', 'ThS', '8f1c5c4', 'TC-UN01.001'],
['ISS-008', '2026-05-07', '2026-05-13', 'กลาง', 'การลงทะเบียนผู้ใช้ใหม่ล้มเหลวในบางกรณี', 'การตรวจสอบข้อมูลลงทะเบียนไม่ครบถ้วน', 'แก้ไขขั้นตอนการลงทะเบียน', 'ThS', '4433ef1', 'TC-UN01.001'],
['ISS-009', '2026-05-07', '2026-05-13', 'สูง', 'การตรวจสอบสิทธิ์ระดับฐานข้อมูลทำงานไม่ถูกต้อง', 'การกำหนดสิทธิ์เชื่อมต่อฐานข้อมูลไม่สอดคล้องกับบทบาท', 'แก้ไขการตรวจสอบสิทธิ์ db_auth', 'ThS', '8cf1d93', 'TC-UN12.001'],
['ISS-010', '2026-05-14', '2026-05-20', 'กลาง', 'รูปแบบการเขียนโค้ดในแต่ละโมดูลไม่สอดคล้องกัน', 'พัฒนาหลายโมดูลคู่ขนานกัน', 'ทบทวนและปรับรูปแบบการเขียนโค้ดให้สอดคล้องกัน', 'ThS', '91f8bb8', 'TC-UN13.001'],
['ISS-011', '2026-05-15', '2026-05-21', 'สูง', 'การเชิญผู้ใช้ สิทธิ์แอปพลิเคชัน และโควตารายการยังไม่รัดกุม', 'ควบคุมสิทธิ์และโควตาถูกเพิ่มภายหลัง', 'แก้ไขการเชิญผู้ใช้ เพิ่มการควบคุมสิทธิ์และโควตารายการ', 'ThS', '6eeebfe', 'TC-UN12.003'],
['ISS-012', '2026-05-15', '2026-05-21', 'สูง', 'StockManager แปลงค่า Lot/Serial ผิดพลาด ทำให้สอบกลับไม่ถูกต้อง', 'การแปลงชนิดข้อมูลไม่รองรับค่าที่เป็นข้อความ', 'แก้ไขการแปลงค่า Lot/Serial และเพิ่มชุดทดสอบวงจรเอกสาร', 'ThS', '94032dd', 'TC-UN04.004'],
['ISS-013', '2026-05-15', '2026-05-21', 'กลาง', 'ยังพบข้อบกพร่องของขั้นตอน Onboarding เพิ่มเติม', 'เส้นทางการรับคำเชิญมีหลายเงื่อนไข', 'แก้ไขข้อบกพร่องของ Onboarding ที่พบเพิ่ม', 'ThS', 'b76dc67', 'TC-UN01.001'],
['ISS-014', '2026-05-17', '2026-05-23', 'กลาง', 'เส้นทางไฟล์ (File Path) ไม่ถูกต้องในบางสภาพแวดล้อม', 'สมมติฐานเส้นทางติดตั้งไม่ตรงกัน', 'แก้ไขเส้นทางไฟล์ให้ถูกต้องทั้งระบบ', 'ThS', '59037b5, f4ef776', 'TC-UN13.001'],
['ISS-015', '2026-05-17', '2026-05-23', 'สูง', 'ผลตรวจโค้ดพบปัญหา require_once, ขั้นตอนจ่ายออก และ Role Guard', 'การควบคุมข้ามโมดูลไม่สอดคล้องกัน', 'แก้ไข require_once, ขั้นตอนจ่ายออก และเพิ่ม Role Guard', 'ThS', 'b07882e', 'TC-UN01.002'],
['ISS-016', '2026-05-18', '2026-05-24', 'สูง', 'อนุญาตให้เข้าสู่ระบบพร้อมกันหลาย Session', 'ยังไม่มีการบังคับ Session เดียวต่อบัญชี', 'บล็อกการเข้าสู่ระบบซ้ำซ้อนและปรับนโยบายยืนยันตัวตนตามบทบาท', 'ThS', '2930973', 'TC-UN01.003'],
['ISS-017', '2026-05-19', '2026-05-25', 'สูง', 'ยังพบช่องว่างของการควบคุมการเข้าสู่ระบบ', 'บางเส้นทางเข้าสู่ระบบไม่ผ่านการตรวจสอบเดียวกัน', 'ปิดช่องว่างการเข้าสู่ระบบที่พบ', 'ThS', '4733c78', 'TC-UN01.002'],
['ISS-018', '2026-05-20', '2026-05-26', 'สูง', 'ขั้นตอน Onboarding ผู้ใช้ที่ถูกเชิญต้องเพิ่มความปลอดภัย', 'มีหลายเส้นทางที่ต้องบังคับใช้การควบคุมเดียวกัน', 'ดำเนินการ Security Hardening รายการ C1–N7', 'ThS', 'b4b1f5c', 'TC-UN01.001'],
['ISS-019', '2026-05-20', '2026-05-26', 'สูง', 'วงจรชีวิตเอกสารในระบบไม่ตรงกับข้อกำหนด', 'ข้อกำหนดและการพัฒนาเปลี่ยนแปลงไม่พร้อมกัน', 'ปรับข้อกำหนดและแก้โค้ดรายการ C2/M9', 'ThS', 'cb36d3b', 'TC-UN10.002'],
['ISS-020', '2026-05-20', '2026-05-26', 'สูง', 'ข้อมูลหลักมีช่องว่างระหว่างข้อกำหนดและการพัฒนา', 'ข้อกำหนดข้อมูลหลักถูกปรับระหว่างพัฒนา', 'ปรับข้อกำหนดและแก้โค้ดรายการ C1/C2/M3–M6', 'ThS', 'dfeb575', 'TC-UN03.001'],
['ISS-021', '2026-05-21', '2026-05-27', 'กลาง', 'งานตามกำหนดเวลาของ Node.js ทำงานผิดพลาด', 'การตั้งค่า Cron ของบริการ Node.js ไม่ถูกต้อง', 'แก้ไขการทำงานของ Cron บน Node.js', 'ThS', 'f3c0e3c, 714b70d', 'TC-UN11.002'],
['ISS-022', '2026-05-21', '2026-05-27', 'กลาง', 'เอกสารประกอบภายในโครงการยังไม่ครบถ้วน', 'เอกสารถูกปรับตามการพัฒนาอย่างต่อเนื่อง', 'ทบทวนเอกสารทั้งหมดและแก้ไขส่วนที่ขาด', 'ThS', '99ae35d', 'TC-UN13.001'],
['ISS-023', '2026-05-22', '2026-05-28', 'กลาง', 'การเปลี่ยนคำว่า Rack เป็น Bin ไม่ครบทุกจุด และ ReportManager ผิดพลาด', 'เปลี่ยนชื่อไม่ครบทุกไฟล์และป้ายกำกับหน้าจอ', 'เปลี่ยนชื่อไฟล์ ป้ายกำกับ และแก้คุณสมบัติของ ReportManager', 'ThS', '5df6736', 'TC-UN03.001'],
['ISS-024', '2026-05-22', '2026-05-28', 'สูง', 'การเข้าถึงตารางสต๊อกยังไม่จำกัดตามคลังของบริษัท', 'ขอบเขตสิทธิ์คลังสินค้ายังไม่ครบทุกคำสั่ง', 'จำกัดการเข้าถึงตารางสต๊อกตามคลังที่บริษัทได้รับสิทธิ์', 'ThS', '9a50238', 'TC-UN12.001'],
['ISS-025', '2026-05-22', '2026-05-28', 'ต่ำ', 'แถบเลื่อนแนวนอนเกินขอบหน้าจอจากระยะขอบ Sidebar', 'ค่าระยะขอบเกินความกว้างของ Viewport', 'แก้ไขการจัดวาง Sidebar ให้อยู่ในขอบหน้าจอ', 'ThS', 'ed3dd2f', 'TC-UN01.002'],
['ISS-026', '2026-05-22', '2026-05-28', 'สูง', 'ระบบยังรับการเข้าสู่ระบบใหม่ขณะบัญชีมี Session ทำงานอยู่', 'การบังคับ Session เดียวยังไม่ครอบคลุมทุกกรณี', 'ปฏิเสธการเข้าสู่ระบบใหม่เมื่อบัญชีมี Session ทำงานอยู่', 'ThS', 'fda211b', 'TC-UN01.003'],
['ISS-027', '2026-05-23', '2026-05-29', 'ต่ำ', 'เมธอดของคลาสมีการทำงานซ้ำซ้อน', 'พัฒนาเพิ่มทีละส่วนทำให้เกิดความซ้ำซ้อน', 'ลดความซ้ำซ้อนของเมธอดในคลาส', 'ThS', 'a0677d6', 'TC-UN13.001'],
['ISS-028', '2026-07-28', '2026-08-03', 'สูง', 'การเข้าสู่ระบบและค่าตั้งค่าสภาพแวดล้อมผิดพลาดหลัง Baseline', 'ค่าตั้งค่าของสภาพแวดล้อมใหม่ไม่ตรงกับที่ระบบคาดหวัง', 'แก้ไขการเข้าสู่ระบบและค่าตั้งค่าสภาพแวดล้อม', 'ThS', 'b2c4374', 'TC-UN12.004'],
];
/** Change Reports: one document each. */
export const CHANGES = [
{
id: 'CH-001', date: '2026-05-21', file: 'Rack to Bin Rename',
titleTh: 'เปลี่ยนคำเรียกตำแหน่งจัดเก็บจาก Rack เป็น Bin',
requester: P.NoC, unit: 'ฝ่ายวิเคราะห์ระบบ / ตัวแทนผู้ใช้งานคลังสินค้า',
type: 'Scope', typeChecked: 'Quality', priority: 'Medium',
description: 'เปลี่ยนคำเรียกตำแหน่งจัดเก็บสินค้าจาก "Rack" เป็น "Bin" ทั้งระบบ ครอบคลุมป้ายกำกับหน้าจอ, custom.js, เครื่องมือบาร์โค้ด (BarcodeManager.php, location_barcode_label.php, retrieve_rack.php) และคลาสจัดการคลังสินค้า สต๊อก ใบสั่งขาย รายงาน ใบรับคืน ใบคืนผู้ขาย สินค้า และการตั้งค่าบริษัท',
justification: 'ให้คำที่ใช้ในระบบตรงกับคำที่พนักงานคลังสินค้าใช้จริงในการปฏิบัติงาน ลดความสับสนระหว่างการใช้งานและการอบรม',
impact: [
['ผลกระทบต่อ Scope', 'แก้ไข 14 ไฟล์ ประมาณ 1,000 บรรทัด ครอบคลุมคลาสจัดการ 8 คลาสและเครื่องมือบาร์โค้ด ไม่มีการเพิ่มฟังก์ชันใหม่'],
['ผลกระทบต่อ Schedule', 'ดำเนินการภายในช่วงพัฒนาปัจจุบัน ไม่กระทบกำหนดส่งมอบ'],
['ผลกระทบต่อ Budget/Cost', 'ไม่มีค่าใช้จ่ายเพิ่ม ใช้ทรัพยากรทีมพัฒนาเดิม'],
['ผลกระทบต่อ Resource', 'ผู้พัฒนา 1 คน'],
['ผลกระทบต่อ Quality/Deliverables', 'เป็นการเปลี่ยนคำเรียกเท่านั้น ไม่เปลี่ยนพฤติกรรมของระบบ ข้อบกพร่องที่เกิดจากการเปลี่ยนไม่ครบให้บันทึกใน Correction Register (ISS-023)'],
],
commit: '8f57ab5', result: 'ดำเนินการแล้วเสร็จ และตรวจสอบผลด้วย TC-UN03.001 ผ่านเมื่อ 10–14 ส.ค. 69',
},
{
id: 'CH-002', date: '2026-08-08', file: 'Demo Data Population',
titleTh: 'เพิ่มชุดข้อมูลสาธิตสำหรับการตรวจรับและสาธิตระบบ',
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: 'ระบบที่ติดตั้งใหม่ไม่มีประวัติรายการ ทำให้ตรวจรับรายงานและขั้นตอนการทำงานไม่ได้ จึงต้องมีข้อมูลตัวอย่างที่ใกล้เคียงการใช้งานจริงสำหรับการตรวจรับและการสาธิต',
impact: [
['ผลกระทบต่อ Scope', 'แก้ไข 9 ไฟล์ เพิ่มประมาณ 2,900 บรรทัด เป็นสคริปต์สร้างข้อมูลใหม่ ไม่แก้ตรรกะหลักของระบบ'],
['ผลกระทบต่อ Schedule', 'ดำเนินการภายในช่วงเตรียมส่งมอบตามแผน'],
['ผลกระทบต่อ Budget/Cost', 'ไม่มีค่าใช้จ่ายเพิ่ม'],
['ผลกระทบต่อ Resource', 'ผู้พัฒนา 1 คน'],
['ผลกระทบต่อ Quality/Deliverables', 'เป็นการเพิ่มข้อมูลสาธิตเท่านั้น ข้อมูลสาธิตต้องแยกจากข้อมูลใช้งานจริงตาม CR07:004'],
],
commit: 'dd48a8b', result: 'ดำเนินการแล้วเสร็จเมื่อ 14 ส.ค. 69 และใช้เป็นข้อมูลตั้งต้นของการทดสอบและ UAT',
},
{
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 ไฟล์'],
['ผลกระทบต่อ Schedule', 'ดำเนินการภายในช่วงเตรียมส่งมอบ 15–17 ส.ค. 69'],
['ผลกระทบต่อ Budget/Cost', 'ไม่มีค่าใช้จ่ายเพิ่ม'],
['ผลกระทบต่อ Resource', 'ผู้พัฒนา 1 คน'],
['ผลกระทบต่อ Quality/Deliverables', 'ภาพหัวเอกสารถูกใช้ในเอกสารส่งมอบ ชุด Docker Compose สร้างค่าความลับจาก .env ขณะเริ่ม Container ตาม CR10:003'],
],
commit: '63cea23, 136084f, 6c39700', result: 'ดำเนินการแล้วเสร็จเมื่อ 17 ส.ค. 69 และตรวจสอบผลด้วย TC-UN13.002 และ TC-UN13.003',
},
];
/**
* Progress Status Records — one per reporting period.
* [date, from, to, weekNo, taskRows[[taskId, status, pct, remark]], issues[ISS ids], changes[CH ids], next{date,topic}]
*/
export const PERIODS = [
['2026-01-09', '2026-01-05', '2026-01-09', 1, [['1.1', 'Completed', 100, 'เปิดโครงการอย่างเป็นทางการ ชี้แจงขอบเขตและทีมงานครบทุกบทบาท'], ['1.2', 'In Progress', 40, 'อยู่ระหว่างรวบรวมรายชื่อผู้มีส่วนได้ส่วนเสียและระดับการมีส่วนร่วม']], [], [], { date: '2026-01-16', topic: 'ทบทวนรายชื่อผู้มีส่วนได้ส่วนเสียและเริ่มกำหนดวัตถุประสงค์โครงการ' }],
['2026-01-23', '2026-01-12', '2026-01-23', 2, [['1.2', 'Completed', 100, 'จัดทำ Stakeholder Register ครบ 6 บทบาทหลักและผู้ใช้งานที่เกี่ยวข้อง'], ['1.3', 'Completed', 100, 'กำหนดวัตถุประสงค์ ขอบเขตระบบ 11 ระบบงาน และเกณฑ์ความสำเร็จ'], ['1.4', 'Completed', 100, 'Project Charter ได้รับอนุมัติจาก Project Sponsor'], ['2.1', 'In Progress', 30, 'เริ่มเก็บความต้องการลูกค้าจากฝ่ายคลังสินค้า ขาย และบัญชี']], [], [], { date: '2026-02-06', topic: 'สรุปความต้องการลูกค้า CR01–CR14 และทบทวนร่างแผนโครงการ' }],
['2026-02-06', '2026-01-26', '2026-02-06', 3, [['2.1', 'Completed', 100, `สรุปความต้องการลูกค้า ${CRS.length} รายการ จัดหมวด CR01–CR14 และได้ผลสรุป A ทุกรายการ`], ['2.2', 'In Progress', 60, 'อยู่ระหว่างจัดทำ Software Project Plan และ Work Schedule']], [], [], { date: '2026-02-13', topic: 'ทบทวน Software Project Plan, Work Schedule และแผนความเสี่ยง' }],
['2026-02-18', '2026-02-09', '2026-02-18', 4, [['2.2', 'Completed', 100, 'จัดทำแผนโครงการ ทรัพยากร ความเสี่ยง R1–R8 และแผนสำรองงานที่ไม่แล้วเสร็จ'], ['2.3', 'Completed', 100, 'ตั้ง Baseline ความต้องการและแผนงาน อนุมัติเริ่มพัฒนา 19 ก.พ. 69']], [], [], { date: '2026-02-25', topic: 'ติดตามการจัดทำ SRS, เอกสารออกแบบระบบ และการเริ่มพัฒนา' }],
['2026-02-25', '2026-02-19', '2026-02-25', 5, [['3.1', 'Completed', 100, 'จัดทำ SRS (SR01–SR09) และเอกสารออกแบบระบบ เริ่มพัฒนาโครงสร้างแอปพลิเคชัน'], ['4.2', 'In Progress', 20, 'เริ่มควบคุม Source Code บน Git และกำหนด main เป็น Baseline']], [], [], { date: '2026-03-17', topic: 'ติดตามผลการแก้ไขด้านความปลอดภัยและการออกแบบฐานข้อมูลสต๊อก' }],
['2026-03-17', '2026-03-09', '2026-03-17', 6, [['3.2', 'Completed', 100, 'ป้องกันไฟล์ตั้งค่า แก้ผลตรวจความปลอดภัย ออกแบบฐานข้อมูลสต๊อกและโมดูล ICS'], ['4.3', 'In Progress', 10, 'เริ่มตรวจสอบเอกสารความต้องการและออกแบบรอบที่ 1']], ['ISS-001'], [], { date: '2026-04-29', topic: 'ติดตามการพัฒนาโมดูลคลังสินค้า สินค้า และรายงาน' }],
['2026-04-29', '2026-04-09', '2026-04-29', 7, [['3.3', 'Completed', 100, 'พัฒนาแดชบอร์ดสต๊อก สินค้า ความจุคลัง Lot/Serial/Expiry และรายงานครบตามแผน'], ['3.4', 'In Progress', 50, 'อยู่ระหว่างพัฒนาเข้าสู่ระบบ ลงทะเบียน และ Onboarding']], ['ISS-002'], [], { date: '2026-05-08', 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-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-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-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-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],
];
/**
* Meeting Records — [date, phase, taskId, taskName, timeFrom-to, attachment, agenda[[topic,purpose]],
* discussion[[issue,detail,ownerIds,status]], actions[[task,ownerIds,due,status]], progress[[area,status,pct,detail]], next{date,topic,organizer} ]
*/
export const MEETINGS = [
{
date: '2026-01-05', phase: 'Project Initiation', taskId: '1.1', taskName: 'Kick-off Meeting', time: '09:00 – 11:00 น.',
attachment: '-',
progress: [['Phase 1: Project Initiation', 'กำลังดำเนินการ', 20, 'เปิดโครงการและชี้แจงทีมงานเรียบร้อย'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 5, 'โครงการเริ่มต้นตามกำหนด พร้อมเข้าสู่ขั้นตอน Identify Stakeholders']],
agenda: [['แนะนำสมาชิกทีมโครงการ', 'สร้างความเข้าใจบทบาทและหน้าที่'], ['ทบทวนความจำเป็นและวัตถุประสงค์ของระบบคลังสินค้า', 'กำหนดเป้าหมายร่วมกัน'], ['ภาพรวมระบบงาน 11 ระบบที่จะพัฒนา', 'เห็นขอบเขตและแผนการพัฒนา'], ['สรุปแผนโครงการ 232 วัน', 'ยืนยัน Timeline 5 ม.ค. – 24 ส.ค. 69'], ['กำหนดรอบการประชุมและการรายงานความก้าวหน้า', 'ตั้งรอบประชุม ผู้รับผิดชอบ และเอกสารอ้างอิง']],
discussion: [['วัตถุประสงค์โครงการ', 'พัฒนาระบบบริหารจัดการคลังสินค้าแบบรวมศูนย์ รองรับหลายบริษัทและหลายคลัง', ['SeV', 'ApS'], 'ยืนยันแล้ว'], ['ระยะเวลาโครงการ', 'ระยะเวลา 232 วัน ตั้งแต่ 5 ม.ค. – 24 ส.ค. 69', ['ApS'], 'อนุมัติ'], ['ทรัพยากรและทีมงาน', 'ทีมหลัก 6 คน ครบบทบาท PM, SA, Developer, QA, Document Control และ Project Sponsor', ['ApS', 'YaB'], 'ผ่านการอนุมัติ'], ['Project Repository', 'ใช้ Git Server เป็นศูนย์กลางเก็บ Source Code และเอกสารโครงการ', ['ThS', 'YaB'], 'ดำเนินการแล้ว']],
actions: [['จัดทำ Stakeholder Register (Task 1.2)', ['ApS', 'YaB'], '2026-01-16', 'Scheduled'], ['เตรียม Repository และโครงสร้างโฟลเดอร์เอกสารโครงการ', ['ThS'], '2026-01-16', 'Scheduled']],
next: { date: '2026-01-16', topic: 'ทบทวนรายชื่อผู้มีส่วนได้ส่วนเสียและกำหนดวัตถุประสงค์โครงการ' },
},
{
date: '2026-01-23', phase: 'Project Initiation', taskId: '1.4', taskName: 'Approve Project Charter', time: '09:00 – 11:00 น.',
attachment: 'Project Charter Report, Stakeholder Register',
progress: [['Phase 1: Project Initiation', 'เสร็จสมบูรณ์', 100, 'อนุมัติ Project Charter และขอบเขตงานครบถ้วน'], ['Phase 2: Project Planning', 'กำลังดำเนินการ', 30, 'อยู่ระหว่างเก็บความต้องการลูกค้า'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 12, 'เข้าสู่ขั้นตอนวางแผนโครงการตามกำหนด']],
agenda: [['พิจารณาอนุมัติกฎบัตรโครงการ (Project Charter)', 'ยืนยันวัตถุประสงค์ ขอบเขต และงบประมาณ'], ['ทบทวน Stakeholder Register', 'ยืนยันบทบาทและระดับการมีส่วนร่วม'], ['แผนการเก็บความต้องการลูกค้า', 'กำหนดผู้ให้ข้อมูลและกรอบเวลา']],
discussion: [['อนุมัติกฎบัตรโครงการ', 'Project Sponsor อนุมัติวัตถุประสงค์ ขอบเขต 11 ระบบงาน และเกณฑ์ความสำเร็จ', ['SeV'], 'อนุมัติ'], ['ผู้มีส่วนได้ส่วนเสีย', 'ยืนยันผู้รับผิดชอบตาม RACI ครบทุกบทบาท', ['ApS', 'YaB'], 'ยืนยันแล้ว'], ['การเก็บความต้องการ', 'มอบหมาย System Analyst สัมภาษณ์ฝ่ายคลังสินค้า ขาย จัดซื้อ และบัญชี', ['NoC'], 'ดำเนินการ']],
actions: [['เก็บและสรุปความต้องการลูกค้า CR01–CR14 (Task 2.1)', ['NoC', 'ApS'], '2026-02-06', 'Scheduled'], ['จัดทำร่าง Software Project Plan และ Work Schedule (Task 2.2)', ['ApS'], '2026-02-13', 'Scheduled']],
next: { date: '2026-02-06', topic: 'สรุปความต้องการลูกค้าและทบทวนร่างแผนโครงการ' },
},
{
date: '2026-02-18', phase: 'Project Planning', taskId: '2.3', taskName: 'Baseline Requirements & Schedule', time: '09:00 – 11:00 น.',
attachment: 'Customer Requirements, Software Project Plan, Work Schedule',
progress: [['Phase 1: Project Initiation', 'เสร็จสมบูรณ์', 100, 'ปิดขั้นตอนเริ่มต้นโครงการ'], ['Phase 2: Project Planning', 'เสร็จสมบูรณ์', 100, 'ตั้ง Baseline ความต้องการและแผนงานเรียบร้อย'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 20, 'พร้อมเริ่มพัฒนาระบบ 19 ก.พ. 69']],
agenda: [['ทบทวนความต้องการลูกค้า CR01–CR14', 'ยืนยันความครบถ้วนและความเป็นไปได้'], ['ทบทวน Software Project Plan และ Work Schedule', 'ยืนยันแผนงาน ทรัพยากร และความเสี่ยง'], ['อนุมัติเริ่มพัฒนาระบบ', 'ยืนยันวันเริ่มพัฒนาและการควบคุม Source Code']],
discussion: [['ความต้องการลูกค้า', `ความต้องการ ${CRS.length} รายการ จัดหมวด CR01–CR14 ผลสรุปยอมรับ (A) ทุกรายการ`, ['NoC', 'SeV'], 'อนุมัติ'], ['แผนโครงการและความเสี่ยง', 'ยืนยันแผน 232 วัน ความเสี่ยง R1–R8 และแผนสำรองงานที่ไม่แล้วเสร็จ', ['ApS'], 'อนุมัติ'], ['การควบคุม Source Code', 'กำหนด main เป็น Baseline หลัก ไม่นำค่าตั้งค่าและความลับเข้า Git', ['ThS', 'YaB'], 'บังคับใช้ทันที']],
actions: [['เริ่มพัฒนาระบบตาม Task 3.1 ในวันที่ 19 ก.พ. 69', ['ThS', 'NoC'], '2026-02-25', 'Scheduled'], ['จัดทำ SRS และเอกสารออกแบบระบบ', ['NoC', 'ThS'], '2026-02-25', 'Scheduled'], ['รายงานความก้าวหน้าตามรหัสงานใน Work Schedule', ['ApS'], '2026-08-24', 'Ongoing']],
next: { date: '2026-02-25', topic: 'ติดตามการจัดทำ SRS เอกสารออกแบบ และการเริ่มพัฒนาระบบ' },
},
{
date: '2026-03-17', phase: 'Project Execution', taskId: '3.2', taskName: 'Security & Database Foundation', time: '09:00 – 11:00 น.',
attachment: 'Progress Status Record 17 มี.ค. 69',
progress: [['Phase 3: Project Execution', 'กำลังดำเนินการ', 25, 'พัฒนาฐานข้อมูลสต๊อกและโมดูล ICS เบื้องต้น'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 30, 'การพัฒนาเป็นไปตามแผน']],
agenda: [['ผลการแก้ไขตามผลตรวจความปลอดภัย', 'ยืนยันการปิดข้อบกพร่อง ISS-001'], ['การออกแบบฐานข้อมูลสต๊อกและโมดูล ICS', 'ทบทวนโครงสร้างตารางและความสัมพันธ์'], ['แผนพัฒนาโมดูลคลังสินค้าและรายงาน', 'ยืนยันลำดับงานช่วงเดือนเมษายน']],
discussion: [['ความปลอดภัยของระบบ', 'ถอดไฟล์ตั้งค่าออกจาก Git และแก้ไขตามผลตรวจความปลอดภัยครบถ้วน', ['ThS'], 'เสร็จสิ้น'], ['ฐานข้อมูลสต๊อก', 'ยืนยันโครงสร้างตาราง td_stock และตารางข้อมูลหลักที่เกี่ยวข้อง', ['NoC', 'ThS'], 'ยืนยันแล้ว'], ['แผนงานถัดไป', 'พัฒนาแดชบอร์ดสต๊อก สินค้า ความจุคลัง และรายงาน', ['ThS'], 'ดำเนินการ']],
actions: [['บันทึกการแก้ไข ISS-001 ใน Correction Register', ['YaB'], '2026-03-20', 'Scheduled'], ['พัฒนาโมดูลคลังสินค้าและรายงาน (Task 3.3)', ['ThS'], '2026-04-29', 'Scheduled']],
next: { date: '2026-04-29', topic: 'ติดตามการพัฒนาโมดูลคลังสินค้า สินค้า และรายงาน' },
},
{
date: '2026-04-29', phase: 'Project Execution', taskId: '3.3', taskName: 'Inventory & Warehouse Modules', time: '09:00 – 11:00 น.',
attachment: 'Progress Status Record 29 เม.ย. 69',
progress: [['Phase 3: Project Execution', 'กำลังดำเนินการ', 50, 'พัฒนาโมดูลคลังสินค้า สินค้า Lot/Serial/Expiry และรายงานเสร็จ'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 45, 'เข้าสู่การพัฒนาระบบสิทธิ์และเอกสารธุรกิจ']],
agenda: [['ผลการพัฒนาโมดูลคลังสินค้าและรายงาน', 'ตรวจสอบความครบถ้วนตาม CR01:005–CR01:012'], ['ประเด็นความถูกต้องของข้อมูล', 'พิจารณาการแก้ไข ISS-002'], ['แผนพัฒนาระบบสิทธิ์ ใบสั่งขาย และบาร์โค้ด', 'ยืนยันลำดับงานช่วงต้นเดือนพฤษภาคม']],
discussion: [['โมดูลคลังสินค้า', 'พัฒนาแดชบอร์ดสต๊อก ความจุคลัง Lot/Serial/Expiry และรายงานครบตามความต้องการ', ['ThS', 'NoC'], 'เสร็จสิ้น'], ['ความถูกต้องของข้อมูล', 'ถอดคำสั่งที่กระทบความถูกต้องและเพิ่มการตรวจสอบก่อนทำรายการ', ['ThS'], 'เสร็จสิ้น'], ['ระบบสิทธิ์ผู้ใช้', 'มอบหมายพัฒนา Role Guard และรวมศูนย์การตรวจสิทธิ์', ['ThS'], 'ดำเนินการ']],
actions: [['บันทึกการแก้ไข ISS-002 ใน Correction Register', ['YaB'], '2026-05-01', 'Scheduled'], ['พัฒนาระบบเข้าสู่ระบบ สิทธิ์ และเอกสารขาย (Task 3.4, 3.5)', ['ThS'], '2026-05-12', 'Scheduled']],
next: { date: '2026-05-08', topic: 'ติดตามการพัฒนาระบบสิทธิ์ ใบสั่งขาย และบาร์โค้ด' },
},
{
date: '2026-05-13', phase: 'Project Execution', taskId: '3.6', taskName: 'Production Preparation & Setup', time: '09:00 – 11:00 น.',
attachment: 'Progress Status Record 13 พ.ค. 69',
progress: [['Phase 3: Project Execution', 'กำลังดำเนินการ', 70, 'ระบบสิทธิ์ เอกสารขาย และสคริปต์ติดตั้งเสร็จ'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 60, 'เริ่มพัฒนาโมดูลบัญชีและการเงิน']],
agenda: [['ผลการพัฒนาระบบสิทธิ์และเอกสารขาย', 'ตรวจสอบความครบถ้วนตาม CR01:001–CR01:004 และ CR01:013'], ['ประเด็นความปลอดภัยและ Onboarding ที่พบ', 'พิจารณาการแก้ไข ISS-003 ถึง ISS-009'], ['แผนพัฒนาบัญชีและการเงิน', 'ยืนยันลำดับงานช่วงกลางเดือนพฤษภาคม']],
discussion: [['ระบบสิทธิ์และ Onboarding', 'แก้ไขข้อบกพร่องการลงทะเบียน Onboarding และการตรวจสิทธิ์ครบทุกรายการที่พบ', ['ThS'], 'เสร็จสิ้น'], ['การเตรียมใช้งานจริง', 'จัดทำ Base URL อัตโนมัติและสคริปต์ติดตั้งฐานข้อมูลในขั้นตอนเดียว', ['ThS'], 'เสร็จสิ้น'], ['บัญชีและการเงิน', 'มอบหมายพัฒนาผังบัญชี GL สมุดรายวัน และเอกสารการเงิน', ['ThS', 'NoC'], 'ดำเนินการ']],
actions: [['บันทึกการแก้ไข ISS-003 ถึง ISS-009 ใน Correction Register', ['YaB'], '2026-05-15', 'Scheduled'], ['พัฒนาโมดูลบัญชีและการเงิน (Task 3.7)', ['ThS'], '2026-05-23', 'Scheduled']],
next: { date: '2026-05-23', topic: 'ติดตามการพัฒนาบัญชี การเงิน และการควบคุมสิทธิ์ผู้ใช้' },
},
{
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',
progress: [['Phase 3: Project Execution', 'กำลังดำเนินการ', 85, 'บัญชีและการเงินเสร็จ เหลือบริการแจ้งเตือนและการทบทวนความปลอดภัย'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 72, 'เตรียมเข้าสู่ Baseline การพัฒนา']],
agenda: [['ผลการพัฒนาบัญชีและการเงิน', 'ตรวจสอบความครบถ้วนตาม CR01:015–CR01:017'], ['พิจารณาคำขอเปลี่ยนแปลง CH-001 (Rack เป็น Bin)', 'ประเมินผลกระทบและอนุมัติ'], ['แผนงานก่อนตั้ง Baseline การพัฒนา', 'ยืนยันงานที่ต้องเสร็จภายใน 29 พ.ค. 69']],
discussion: [['บัญชีและการเงิน', 'พัฒนาผังบัญชี GL สมุดรายวัน รายงานบัญชี ใบวางบิลและใบเสร็จครบตามความต้องการ', ['ThS', 'NoC'], 'เสร็จสิ้น'], ['คำขอเปลี่ยนแปลง CH-001', 'เปลี่ยนคำเรียกตำแหน่งจัดเก็บจาก Rack เป็น Bin ให้ตรงกับคำที่พนักงานคลังใช้จริง ประเมินผลกระทบระดับ Medium ไม่กระทบกำหนดส่งมอบ', ['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']],
next: { date: '2026-05-29', topic: 'สรุปผลการพัฒนาและตั้ง Baseline การพัฒนา' },
},
{
date: '2026-05-29', phase: 'Project Execution', taskId: '3.10', taskName: 'Refactor & Development Baseline', time: '09:00 – 11:00 น.',
attachment: 'Progress Status Record 29 พ.ค. 69',
progress: [['Phase 3: Project Execution', 'เสร็จสมบูรณ์', 100, 'พัฒนาครบทุกโมดูลและตั้ง Baseline การพัฒนา (Git a0677d6)'], ['Phase 4: Verification & Validation', 'กำลังดำเนินการ', 10, 'เริ่มตรวจสอบเอกสารและจัดทำคู่มือ'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 78, 'เข้าสู่ขั้นตอนตรวจสอบและจัดทำเอกสาร']],
agenda: [['สรุปผลการพัฒนา Task 3.1–3.10', 'ยืนยันความครบถ้วนของการพัฒนา'], ['สรุปผลการทบทวนความปลอดภัยและวงจรเอกสาร', 'ยืนยันการปิดข้อบกพร่อง ISS-016 ถึง ISS-027'], ['แผนการตรวจสอบ ทดสอบ และจัดทำเอกสาร', 'กำหนดผู้รับผิดชอบและกรอบเวลา']],
discussion: [['Baseline การพัฒนา', 'พัฒนาครบทุกโมดูลตามขอบเขต และลดความซ้ำซ้อนของคลาสเรียบร้อย ตั้ง Baseline เมื่อ 29 พ.ค. 69', ['ThS'], 'เสร็จสิ้น'], ['ความปลอดภัยและการแยกข้อมูล', 'ปิดข้อบกพร่องด้านการเข้าสู่ระบบ ขอบเขตบริษัท และวงจรเอกสารครบทุกรายการที่พบ', ['ThS', 'PaNg'], 'เสร็จสิ้น'], ['ขั้นตอนถัดไป', 'ตรวจสอบ Work Products จัดทำคู่มือ และเตรียมชุดทดสอบระบบ', ['PaNg', 'NoC', 'YaB'], 'ดำเนินการ']],
actions: [['จัดทำ Test Case และ Test Procedures ครบทุกหน่วยงาน', ['PaNg', 'NoC'], '2026-07-31', 'Scheduled'], ['จัดทำคู่มือผู้ใช้ คู่มือผู้ดูแลระบบ และคู่มือบำรุงรักษา (Task 4.5)', ['ThS', 'YaB'], '2026-08-17', 'Scheduled'], ['ตรวจสอบ Work Products รอบที่ 1–3 (Task 4.3)', ['PaNg', 'NoC'], '2026-08-15', 'Scheduled']],
next: { date: '2026-08-03', topic: 'ติดตามการตรวจสอบเอกสาร การจัดทำคู่มือ และการแก้ไขหลัง Baseline' },
},
{
date: '2026-08-03', phase: 'Stabilization', taskId: '4.6', taskName: 'Post-baseline Corrections', time: '09:00 – 11:00 น.',
attachment: 'Progress Status Record 3 ส.ค. 69',
progress: [['Phase 4: Verification & Validation', 'กำลังดำเนินการ', 70, 'ตรวจสอบเอกสารรอบที่ 1–3 และจัดทำคู่มือใกล้เสร็จ'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 85, 'เตรียมทดสอบระบบและ UAT']],
agenda: [['ผลการแก้ไขหลัง Baseline การพัฒนา', 'ยืนยันการปิดข้อบกพร่อง ISS-028'], ['ความคืบหน้าการตรวจสอบเอกสารและคู่มือ', 'ทบทวนผลการตรวจสอบรอบที่ 1–3'], ['แผนการทดสอบระบบและ UAT', 'ยืนยันสภาพแวดล้อม ผู้ทดสอบ และกรอบเวลา']],
discussion: [['การแก้ไขหลัง Baseline', 'แก้ไขการเข้าสู่ระบบและค่าตั้งค่าสภาพแวดล้อมที่พบหลังตั้ง Baseline เรียบร้อย', ['ThS'], 'เสร็จสิ้น'], ['การตรวจสอบเอกสาร', 'ตรวจสอบความต้องการ ออกแบบ และการสอบกลับรอบที่ 1–3 พบประเด็นที่ต้องปรับปรุงและแก้ไขแล้ว', ['PaNg', 'NoC'], 'ดำเนินการ'], ['แผนการทดสอบ', 'กำหนดทดสอบระบบบน Internal Testing Server โดย QA และ UAT บนสภาพแวดล้อมจริงโดยตัวแทนลูกค้า ระหว่าง 10–14 ส.ค. 69', ['PaNg', 'SeV'], 'อนุมัติ']],
actions: [['บันทึกการแก้ไข ISS-028 ใน Correction Register', ['YaB'], '2026-08-05', 'Scheduled'], ['เตรียมข้อมูลสาธิตสำหรับการทดสอบและ UAT', ['ThS'], '2026-08-14', 'Scheduled'], ['ดำเนินการทดสอบระบบและ UAT (Task 4.4)', ['PaNg', 'SeV'], '2026-08-14', 'Scheduled']],
next: { date: '2026-08-14', topic: 'สรุปผลการทดสอบระบบ UAT และข้อมูลสาธิต' },
},
{
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',
progress: [['Phase 4: Verification & Validation', 'เสร็จสมบูรณ์', 100, 'ทดสอบ 45 Test Case และ UAT 12 สถานการณ์ ผ่านทั้งหมด'], ['Phase 5: Project Close', 'กำลังดำเนินการ', 60, 'อยู่ระหว่างตรวจสอบ Work Products และเตรียมชุดส่งมอบ'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 92, 'เตรียมตรวจรับและปิดโครงการ']],
agenda: [['สรุปผลการทดสอบระบบ 45 Test Case', 'ยืนยันผลการทดสอบและข้อบกพร่องคงค้าง'], ['สรุปผลการทดสอบการยอมรับ (UAT) 12 สถานการณ์', 'ยืนยันความพร้อมใช้งานจริง'], ['พิจารณาคำขอเปลี่ยนแปลง CH-002 (ข้อมูลสาธิต)', 'ประเมินผลกระทบและอนุมัติ'], ['แผนงานปิดโครงการ', 'ยืนยันงานที่เหลือถึง 24 ส.ค. 69']],
discussion: [['ผลการทดสอบระบบ', 'ทดสอบ 45 Test Case บน Internal Testing Server ผ่านทั้งหมด ไม่พบข้อบกพร่องคงค้าง', ['PaNg'], 'ผ่าน'], ['ผลการทดสอบการยอมรับ', 'ตัวแทนลูกค้าทดสอบ 12 สถานการณ์บนสภาพแวดล้อมใช้งานจริง ผ่านทั้งหมด', ['SeV'], 'ผ่าน'], ['คำขอเปลี่ยนแปลง CH-002', 'เพิ่มชุดข้อมูลสาธิตเพื่อใช้ตรวจรับรายงานและขั้นตอนการทำงาน ประเมินผลกระทบระดับ Medium', ['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']],
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 น.',
attachment: 'Acceptance Report, List of Evidence, Verification Results, Change Report CH-003',
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', 'ประเมินผลกระทบและอนุมัติ'], ['พิจารณาตรวจรับส่งมอบระบบ', 'ลงมติผลการตรวจรับ']],
discussion: [['ผลการดำเนินโครงการ', 'พัฒนาระบบครบตามขอบเขต 11 ระบบงาน ทดสอบและ UAT ผ่านทั้งหมด ไม่มีข้อบกพร่องระดับวิกฤตคงค้าง', ['ApS', 'SeV'], 'ผ่านการรับรอง'], ['ความครบถ้วนของเอกสาร', 'ตรวจสอบ Work Products รอบที่ 4 ครบทุกรายการ ผลผ่านทั้งหมด และจัดทำ List of Evidence เรียบร้อย', ['YaB', 'PaNg'], 'สมบูรณ์'], ['คำขอเปลี่ยนแปลง CH-003', 'ปรับอัตลักษณ์องค์กรและเพิ่มชุดติดตั้ง Docker Compose ประเมินผลกระทบระดับ Medium', ['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']],
next: { date: '2026-08-24', topic: 'สรุปผลการอบรม การปิดงานควบคุมปฏิบัติการ และปิดโครงการอย่างเป็นทางการ' },
},
{
date: '2026-08-24', phase: 'Project Close', taskId: '5.3', taskName: 'Final Report & Sign-off', time: '09:00 – 11:00 น.',
attachment: 'Training Report, Product Operation Guide, Project Repository (Backup)',
progress: [['Phase 1: Project Initiation', 'เสร็จสมบูรณ์', 100, 'เปิดโครงการและอนุมัติกฎบัตรเรียบร้อย'], ['Phase 2: Project Planning', 'เสร็จสมบูรณ์', 100, 'อนุมัติแผนโครงการและ Baseline ความต้องการ'], ['Phase 3: Project Execution', 'เสร็จสมบูรณ์', 100, 'พัฒนาระบบครบตามขอบเขตและเปิดใช้งานจริง'], ['Phase 4: Verification & Validation', 'เสร็จสมบูรณ์', 100, 'ตรวจสอบ ทดสอบ และ UAT ผ่านครบทุกรายการ'], ['Phase 5: Project Close', 'เสร็จสมบูรณ์', 100, 'อบรม ปิดงานควบคุมปฏิบัติการ และปิดโครงการเรียบร้อย'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 100, 'โครงการเสร็จสมบูรณ์ ปิดโครงการอย่างเป็นทางการ']],
agenda: [['สรุปผลการอบรมผู้ใช้งาน', 'ยืนยันความพร้อมของผู้ใช้ก่อนเปิดใช้งานจริง'], ['สรุปการปิดงานควบคุมการสำรองข้อมูลและการเฝ้าระวัง', 'ยืนยันการปิดรายการ OP-001 และ OP-002'], ['สรุปการสำรอง Repository และเอกสารโครงการ', 'ยืนยันการสำรองและกู้คืนได้จริง'], ['ปิดโครงการอย่างเป็นทางการ', 'ลงมติปิดโครงการและส่งมอบเอกสาร']],
discussion: [['ผลการอบรม', 'อบรมผู้ใช้งาน 6 คน เมื่อ 22 ส.ค. 69 ผู้เข้าอบรมทดลองใช้งานครบทุกขั้นตอนได้สำเร็จ', ['ThS', 'PaNg'], 'เสร็จสิ้น'], ['การควบคุมปฏิบัติการ', 'ปิดรายการสำรองฐานข้อมูลอัตโนมัติ (OP-001) และการเฝ้าระวังพร้อมแจ้งเตือน (OP-002) เมื่อ 23 ส.ค. 69', ['ThS'], 'เสร็จสิ้น'], ['การสำรองข้อมูลโครงการ', 'ซิงก์ Branch และ Tag sdlc-v1.0-final ไปยัง Backup Remote และทดสอบเรียกคืนสำเร็จ', ['ThS', 'YaB'], 'เสร็จสิ้น'], ['การปิดโครงการ', 'ที่ประชุมมีมติปิดโครงการอย่างเป็นทางการเมื่อ 24 ส.ค. 69', ['SeV', 'ApS'], 'ปิดโครงการ']],
actions: [['จัดเก็บเอกสารโครงการทั้งหมดเข้าระบบควบคุมเอกสาร', ['YaB'], '2026-08-24', 'เสร็จสมบูรณ์'], ['ติดตามการตรวจนับสต๊อกสิ้นเดือนแรกหลังเปิดใช้งานจริง', ['ApS'], '2026-09-30', 'Scheduled']],
next: null,
},
];
+60
View File
@@ -0,0 +1,60 @@
// Shared helpers for the ISO/IEC 29110 work-product seed.
// Every document follows the layout of the audited 200-TAS-25-001-00 package:
// title, document-control block, Thai section headings, tables, three signature tables.
export const TH_MONTHS = ['มกราคม','กุมภาพันธ์','มีนาคม','เมษายน','พฤษภาคม','มิถุนายน','กรกฎาคม','สิงหาคม','กันยายน','ตุลาคม','พฤศจิกายน','ธันวาคม'];
export const TH_SHORT = ['ม.ค.','ก.พ.','มี.ค.','เม.ย.','พ.ค.','มิ.ย.','ก.ค.','ส.ค.','ก.ย.','ต.ค.','พ.ย.','ธ.ค.'];
const parts = (iso) => iso.split('-').map(Number);
/** 2026-08-17 -> 25690817 (Buddhist compact, used in filenames and Release fields). */
export const bud = (iso) => { const [y, m, d] = parts(iso); return `${y + 543}${String(m).padStart(2, '0')}${String(d).padStart(2, '0')}`; };
/** 2026-08-17 -> 17 สิงหาคม 2569 */
export const thLong = (iso) => { const [y, m, d] = parts(iso); return `${d} ${TH_MONTHS[m - 1]} ${y + 543}`; };
/** 2026-08-17 -> 17 ส.ค. 69 */
export const thShort = (iso) => { const [y, m, d] = parts(iso); return `${d} ${TH_SHORT[m - 1]} ${String(y + 543).slice(2)}`; };
/** 2026-08-17 -> 17/08/2569 */
export const thNum = (iso) => { const [y, m, d] = parts(iso); return `${String(d).padStart(2, '0')}/${String(m).padStart(2, '0')}/${y + 543}`; };
/** Range in short Thai form: 10–14 ส.ค. 69 or 30 พ.ค.–31 ก.ค. 69 */
export const thRange = (a, b) => {
if (a === b) return thShort(a);
const [ya, ma, da] = parts(a); const [yb, mb, db] = parts(b);
if (ya === yb && ma === mb) return `${da}–${db} ${TH_SHORT[ma - 1]} ${String(ya + 543).slice(2)}`;
if (ya === yb) return `${da} ${TH_SHORT[ma - 1]}–${db} ${TH_SHORT[mb - 1]} ${String(ya + 543).slice(2)}`;
return `${thShort(a)}–${thShort(b)}`;
};
export const thRangeLong = (a, b) => `${thLong(a)} – ${thLong(b)}`;
export const daysBetween = (a, b) => Math.round((Date.parse(b) - Date.parse(a)) / 86400000) + 1;
const cell = (v) => String(v ?? '').replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
/** Markdown table. `align` is an optional array of 'l' | 'c' | 'r'. */
export function table(headers, rows, align) {
const sep = headers.map((_, i) => ({ c: ':---:', r: '---:' }[align?.[i]] ?? '---'));
const line = (r) => `| ${r.map(cell).join(' | ')} |`;
return [line(headers), line(sep), ...rows.map(line)].join('\n');
}
/** Document-control block. Each row is [label, value] or [label, value, label2, value2]. */
export function control(rows) {
return rows.map((r) => `| ${r.map(cell).join(' | ')} |`).join('\n');
}
/** A single signature table (ชื่อ / ตำแหน่ง / ลายเซ็น / วันที่). */
const sigTable = (people) => table(['ชื่อ', 'ตำแหน่ง', 'ลายเซ็น', 'วันที่'], people.map((p) => [p.th, p.role, '', '']));
/** The three signature blocks used by every work product. */
export function signatures(secretary, reviewer, approver) {
const list = (x) => (Array.isArray(x) ? x : [x]);
return [
'## ผู้จัดทำเอกสาร (Secretary)', sigTable(list(secretary)),
'## ผู้ตรวจสอบเอกสาร (Reviewer)', sigTable(list(reviewer)),
'## ผู้อนุมัติ (Approval)', sigTable(list(approver)),
].join('\n\n');
}
export const bullets = (items) => items.map((i) => `- ${i}`).join('\n');
export const numbered = (items) => items.map((i, n) => `${n + 1}. ${i}`).join('\n');
/** Join document parts with blank lines, dropping empty parts. */
export const doc = (...parts) => parts.flat().filter((p) => p !== undefined && p !== null && p !== '').join('\n\n') + '\n';
+180
View File
@@ -0,0 +1,180 @@
// Project facts shared by every work product. Edit here, then run `node scripts/sdlc-seed/build.mjs`.
export const CODE = '200-WMS-26-001-00';
export const PROJECT_TH = 'โครงการพัฒนาระบบบริหารจัดการคลังสินค้า (BRN WMS) บริษัท บี.อาร์.เอ็น เอ็นเตอร์ไพรส์ จำกัด';
export const COMPANY_TH = 'บริษัท บี.อาร์.เอ็น เอ็นเตอร์ไพรส์ จำกัด';
export const START = '2026-01-05';
export const END = '2026-08-24';
export const DEV_START = '2026-02-19';
export const DEV_BASELINE = '2026-05-29';
export const TEST_FROM = '2026-08-10';
export const TEST_TO = '2026-08-14';
export const ACCEPT_DATE = '2026-08-17';
export const TRAINING_DATE = '2026-08-22';
export const OPS_CLOSE_DATE = '2026-08-23';
export const RELEASE_DATE = '2026-08-17'; // date stamped on the consolidated V1.0 issue
export const BASELINE_COMMIT = '6c39700'; // delivered software baseline on main
export const TEST_STATE_COMMIT = 'dd48a8b'; // closest retained repository state at end of test window
export const TAG = 'sdlc-v1.0-final';
export const ORIGIN = 'git@188.166.228.62:nok/wms-app.git';
export const BACKUP = 'git@github.com:thanakorninbox-dev/wms-app.git';
export const P = {
SeV: { id: 'SeV', th: 'คุณเสรี วิริยะสกุลธรณ์', en: 'Seri Viriyasakultorn', role: 'Project Sponsor', pos: 'กรรมการผู้จัดการ (CEO)', email: 'seri.v@brn.co.th' },
ApS: { id: 'ApS', th: 'คุณอภิรัชช์ สุภัทรประทีป', en: 'Apirach Supattaratpateep', role: 'Project Manager', pos: 'ผู้จัดการโครงการ', email: 'apirach.s@brn.co.th' },
NoC: { id: 'NoC', th: 'คุณนพพงษ์ เจริญสุข', en: 'Noppong Chareunsook', role: 'System Analyst', pos: 'นักวิเคราะห์ระบบ', email: 'noppong.c@brn.co.th' },
ThS: { id: 'ThS', th: 'คุณธนกร สถิตวิทยากุล', en: 'Thanakorn Sathitwitayakul', role: 'Developer', pos: 'นักพัฒนาระบบ', email: 'thanakorn.s@brn.co.th' },
PaNg: { id: 'PaNg', th: 'คุณปริญ งามขำ', en: 'Parin Ngamkham', role: 'QA/Tester', pos: 'ผู้ทดสอบระบบ', email: 'parin.n@brn.co.th' },
YaB: { id: 'YaB', th: 'คุณเยาวลักษณ์ บางชมภู', en: 'Yaowalak Bangchomphoo', role: 'Document Control', pos: 'เจ้าหน้าที่ควบคุมเอกสาร', email: 'yaowalak.b@brn.co.th' },
};
export const TEAM = [P.SeV, P.ApS, P.NoC, P.ThS, P.PaNg, P.YaB];
export const ORGANIZER = `${P.ApS.th} (Project Manager) , ${P.NoC.th} (System Analyst)`;
export const ORGANIZER_PM = `${P.ApS.th} (Project Manager)`;
export const RECORDER = `${P.YaB.th} (Document Control)`;
/** Deliverables (Work Products) as listed in the plan, acceptance and configuration records. */
export const WPS = [
['WP 1.0', `เอกสาร ${CODE} Software Project Plan (พร้อม Work Schedule)`, 'ส่งเอกสารจำนวน 1 ชุด'],
['WP 2.0', `เอกสาร ${CODE} Customer Requirements`, 'ส่งเอกสารจำนวน 1 ชุด'],
['WP 3.0', `เอกสาร ${CODE} Software Requirements Specification`, 'ส่งเอกสารจำนวน 1 ชุด'],
['WP 4.0', `เอกสาร ${CODE} Software Design`, 'ส่งเอกสารจำนวน 1 ชุด'],
['WP 5.0', `เอกสาร ${CODE} Change Report`, 'ส่งเอกสารจำนวน 3 ฉบับ (CH-001–CH-003)'],
['WP 6.0', `เอกสาร ${CODE} Test Case and Test Procedures`, 'ส่งเอกสารจำนวน 1 ชุด'],
['WP 7.0', `เอกสาร ${CODE} Validation Results`, 'ส่งเอกสารจำนวน 1 ชุด'],
['WP 8.0', `เอกสาร ${CODE} Software User Document`, 'ส่งเอกสารจำนวน 1 ชุด'],
['WP 9.0', 'ระบบ BRN WMS ที่ผ่านการทดสอบพร้อมนำไปใช้งานจริง', `ส่ง Source Code (Git baseline ${BASELINE_COMMIT}) และติดตั้ง`],
['WP 10.0', `เอกสาร ${CODE} Product Operation Guide`, 'ส่งเอกสารจำนวน 1 ชุด'],
['WP 11.0', `เอกสาร ${CODE} Maintenance Document`, 'ส่งเอกสารจำนวน 1 ชุด'],
];
/** Systems / sub-systems in scope (SOW table in the plan and charter). */
export const SCOPE = [
['ระบบบริหารจัดการผู้ใช้งานและสิทธิ์', ['ลงทะเบียนเจ้าของบริษัทและเชิญผู้ใช้งาน (Onboarding)', 'เข้าสู่ระบบและควบคุมสิทธิ์ตามบทบาท Owner / Admin / Staff / Viewer', 'กู้คืนรหัสผ่าน, OTP และควบคุม Session', 'ตั้งค่าบริษัท, SMTP, ผู้ใช้งาน และสิทธิ์การเข้าถึงแอปพลิเคชัน']],
['ระบบข้อมูลหลัก (Master Data)', ['คลังสินค้า, พื้นที่จัดเก็บ, ช่องจัดเก็บ (Bin)', 'หมวดสินค้าและสินค้า', 'ประเภทผู้ติดต่อและผู้ติดต่อ (ลูกค้า/ผู้ขาย)', 'รองรับโครงสร้างคลังแบบชั้นเดียวและแบบหลายชั้น']],
['ระบบควบคุมสินค้าคงคลัง (Inventory Control)', ['รับสินค้าเข้า (Stock-in)', 'จ่ายสินค้าออก (Stock-out) พร้อมตรวจสอบยอดคงเหลือ', 'โอนย้ายสินค้าระหว่างคลัง/ตำแหน่ง', 'ติดตาม Lot, Serial Number และวันหมดอายุ', 'พิมพ์บาร์โค้ดสินค้า/ตำแหน่ง และรองรับการสแกน', 'แนบไฟล์ประกอบรายการ']],
['ระบบขาย (Sales)', ['ใบเสนอราคา (Quotation)', 'ใบสั่งขาย (Sales Order)', 'ใบแจ้งหนี้ (Invoice)', 'ใบรับคืน / ใบลดหนี้ (Return / Credit Note)']],
['ระบบจัดซื้อ (Purchasing)', ['ใบขอซื้อ (Purchase Request)', 'ใบสั่งซื้อ (Purchase Order)', 'ใบแจ้งหนี้ซื้อ (Purchase Invoice)', 'ใบคืนสินค้าผู้ขาย (Supplier Return)']],
['ระบบการเงิน (Finance)', ['ใบวางบิลรับ / ใบเสร็จรับเงิน', 'ใบวางบิลจ่าย / ใบสำคัญจ่าย']],
['ระบบบัญชี (Accounting)', ['ผังบัญชี, แผนก, สูตรบัญชี', 'สมุดรายวันและบัญชีแยกประเภท (Journal / GL)', 'รายงานงบทดลอง, งบกำไรขาดทุน, งบดุล, ภาษีมูลค่าเพิ่ม']],
['ระบบรายงานและแดชบอร์ด', ['แดชบอร์ดคลังสินค้าและบัญชี', 'รายงานสต๊อก, ความเคลื่อนไหว, ความจุคลัง, สินค้าใกล้หมด, สินค้าหมดอายุ, Lot', 'กรอง ดู พิมพ์ และส่งออกรายงาน']],
['ระบบควบคุมเอกสาร', ['ออกเลขที่เอกสารอัตโนมัติตามลำดับที่กำหนด', 'ควบคุมสถานะและวงจรชีวิตของเอกสาร', 'บันทึกผู้สร้าง/ผู้แก้ไข/ประวัติรายการ']],
['ระบบแจ้งเตือนและงานตามกำหนดเวลา', ['แจ้งเตือนแบบ Real-time ผ่าน Node.js / Socket.IO', 'งานสรุปยอดสต๊อก/GL และแจ้งเตือนสินค้าใกล้หมด, ใบแจ้งหนี้ค้างชำระ ตามกำหนดเวลา']],
['ระบบติดตั้งและตั้งค่า', ['ติดตั้งแบบ Manual ผ่าน setup.php', 'ติดตั้งแบบ Container ผ่าน Docker Compose (php-apache, mariadb, node/pm2)', 'สร้างค่าตั้งค่าและความลับของระบบจาก .env โดยไม่เก็บใน Source Control']],
];
/**
* 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 = [
['CR01', 'Feature & Functional Characteristics', [
['CR01:001', 'ระบบต้องรองรับการลงทะเบียนเจ้าของบริษัทและการเชิญผู้ใช้งานเข้าร่วมบริษัท (Onboarding)', 'FR-001'],
['CR01:002', 'ระบบต้องยืนยันตัวตนผู้ใช้และบังคับสิทธิ์ตามบทบาท Owner, Admin, Staff และ Viewer', 'FR-002'],
['CR01:003', 'ระบบต้องรองรับการกู้คืนรหัสผ่าน การควบคุม Session และการยืนยัน OTP ตามที่กำหนด', 'FR-003'],
['CR01:004', 'ระบบต้องให้ผู้ดูแลจัดการข้อมูลบริษัท, SMTP, การตั้งค่าระบบ, ผู้ใช้งาน และสิทธิ์การเข้าถึงแอปพลิเคชัน', 'FR-004'],
['CR01:005', 'ระบบต้องจัดการข้อมูลคลังสินค้า, พื้นที่/ช่องจัดเก็บ, หมวดสินค้า, สินค้า, ประเภทผู้ติดต่อ และผู้ติดต่อ', 'FR-005'],
['CR01:006', 'ระบบต้องรองรับโครงสร้างตำแหน่งจัดเก็บทั้งแบบคลังเดียวและแบบหลายชั้น (คลัง/พื้นที่/ช่อง)', 'FR-006'],
['CR01:007', 'ระบบต้องบันทึกการรับสินค้าเข้า (Stock-in) ระบุสินค้า จำนวน ตำแหน่ง เอกสารอ้างอิง และข้อมูลติดตาม', 'FR-007'],
['CR01:008', 'ระบบต้องบันทึกการจ่ายสินค้าออก (Stock-out) โดยตรวจสอบสิทธิ์และยอดคงเหลือก่อนจ่าย', 'FR-008'],
['CR01:009', 'ระบบต้องโอนย้ายสินค้าระหว่างตำแหน่งจัดเก็บที่ได้รับอนุญาตโดยยอดต้นทาง/ปลายทางสมดุลกัน', 'FR-009'],
['CR01:010', 'ระบบต้องติดตาม Lot, Serial Number และวันหมดอายุของสินค้าที่เกี่ยวข้อง', 'FR-010'],
['CR01:011', 'ระบบต้องแสดงภาพรวมสต๊อก, ประวัติความเคลื่อนไหว, ความจุ/การใช้พื้นที่, สินค้าใกล้หมด, สินค้าหมดอายุ และข้อมูล Lot', 'FR-011'],
['CR01:012', 'ระบบต้องพิมพ์บาร์โค้ดสินค้า (SKU) และตำแหน่งจัดเก็บ และรองรับการสแกนในหน้าจอที่กำหนด', 'FR-012'],
['CR01:013', 'ระบบต้องสร้างและจัดการใบเสนอราคา, ใบสั่งขาย, ใบแจ้งหนี้, ใบรับคืน และใบลดหนี้', 'FR-013'],
['CR01:014', 'ระบบต้องสร้างและจัดการใบขอซื้อ, ใบสั่งซื้อ, ใบแจ้งหนี้ซื้อ และใบคืนสินค้าผู้ขาย', 'FR-014'],
['CR01:015', 'ระบบต้องสร้างและจัดการใบวางบิลรับ, ใบเสร็จรับเงิน, ใบวางบิลจ่าย และใบสำคัญจ่าย', 'FR-015'],
['CR01:016', 'ระบบต้องจัดการผังบัญชี, แผนก, สูตรบัญชี, สมุดรายวัน และบัญชีแยกประเภท', 'FR-016'],
['CR01:017', 'ระบบต้องจัดทำรายงานงบทดลอง, งบกำไรขาดทุน, งบดุล, ภาษีมูลค่าเพิ่ม, สมุดรายวัน และความเคลื่อนไหว GL', 'FR-017'],
['CR01:018', 'ระบบต้องออกเลขที่เอกสารอัตโนมัติและควบคุมสถานะ/วงจรชีวิตของเอกสาร', 'FR-018'],
['CR01:019', 'ระบบต้องรองรับการแนบไฟล์ที่อนุญาตกับรายการที่กำหนด', 'FR-019'],
['CR01:020', 'ระบบต้องให้ผู้ใช้กรอง ดู พิมพ์ และส่งออกรายงานปฏิบัติการและรายงานผู้บริหาร', 'FR-020'],
['CR01:021', 'ระบบต้องแจ้งเตือนผู้ใช้ที่เกี่ยวข้องเมื่อสถานะเอกสารเปลี่ยนหรือมีเหตุการณ์ปฏิบัติการ', 'FR-021'],
['CR01:022', 'ระบบต้องสรุปยอดสต๊อก/GL และแจ้งเตือนสินค้าใกล้หมดและใบแจ้งหนี้ค้างชำระตามกำหนดเวลา', 'FR-022'],
['CR01:023', 'ระบบต้องเก็บผู้สร้าง ผู้แก้ไข สถานะ และประวัติรายการเพื่อการตรวจสอบ', 'FR-023'],
['CR01:024', 'ระบบต้องจำกัดข้อมูลบริษัทและคลังสินค้าให้เฉพาะผู้ใช้ที่ได้รับอนุญาตในบริบทปัจจุบัน', 'FR-024'],
]],
['CR02', 'Performance Considerations', [
['CR02:001', 'ระบบต้องตอบสนองงานประจำวัน (เปิดหน้าจอ, ค้นหา, สร้างเอกสาร) ภายในเวลาที่ใช้งานได้จริงบนสภาพแวดล้อมที่ตกลง', 'NFR-006'],
['CR02:002', 'ระบบต้องมีตารางสรุปยอด (Aggregate) เพื่อให้แดชบอร์ดและรายงานแสดงผลได้โดยไม่ต้องคำนวณใหม่ทุกครั้ง', 'NFR-006'],
['CR02:003', 'ระบบต้องรองรับปริมาณข้อมูลและผู้ใช้พร้อมกันในระดับที่ตกลงสำหรับสภาพแวดล้อมใช้งานจริง', 'NFR-006'],
]],
['CR03', 'Interface Considerations', [
['CR03:001', 'ระบบต้องเชื่อมต่อฐานข้อมูล MySQL/MariaDB 2 ฐาน (wms สำหรับผู้ใช้/บริษัท และ wms2 สำหรับคลัง/บัญชี)', 'Operational context'],
['CR03:002', 'ระบบต้องใช้งานผ่าน Web Browser มาตรฐาน (Chrome, Edge, Firefox) ได้', 'NFR-008'],
['CR03:003', 'ระบบต้องส่งเหตุการณ์ไปยังบริการ Node.js ผ่าน Endpoint ภายในที่ป้องกันด้วย Secret', 'NFR-002'],
['CR03:004', 'Browser ต้องเชื่อมต่อ Socket.IO Endpoint สาธารณะเพื่อรับการแจ้งเตือนแบบ Real-time', 'FR-021'],
['CR03:005', 'ระบบต้องส่งอีเมล Onboarding, กู้คืนรหัสผ่าน และแจ้งเตือนผ่าน SMTP ที่ตั้งค่าต่อบริษัท', 'FR-004'],
]],
['CR04', 'Required System Characteristics', [
['CR04:001', 'ระบบต้องพัฒนาบนสถาปัตยกรรม Web-based Application', 'Operational context'],
['CR04:002', 'ระบบต้องเก็บข้อมูลแบบ Relational Database และรักษา Referential Integrity', 'Data requirements'],
['CR04:003', 'ระบบต้องรองรับการเข้าสู่ระบบด้วย Username/Password และกำหนดบทบาทผู้ใช้', 'FR-002'],
['CR04:004', 'ระบบต้องทำงานบน PHP 8 ขึ้นไป, MySQL/MariaDB และ Node.js', 'NFR-008'],
]],
['CR05', 'Human Engineering Considerations', [
['CR05:001', 'UI ต้องเป็น Responsive ใช้งานได้ทั้งบน Desktop และอุปกรณ์หน้าคลังสินค้า (Tablet/Mobile)', 'NFR-005'],
['CR05:002', 'เมนูและปุ่มคำสั่งต้องแสดงตามบทบาทและสิทธิ์การเข้าถึงของผู้ใช้', 'Interface requirements'],
['CR05:003', 'ระบบต้องแสดงผลการตรวจสอบข้อมูล สถานะ ความสำเร็จ และข้อผิดพลาดอย่างชัดเจน', 'Interface requirements'],
['CR05:004', 'ระบบต้องมีขั้นตอนยืนยันก่อนลบ ยกเลิก หรือทำรายการที่ย้อนกลับไม่ได้', 'Usability practice'],
['CR05:005', 'ระบบต้องพิมพ์เอกสารธุรกิจและฉลากบาร์โค้ดในรูปแบบที่ใช้งานได้', 'Interface requirements'],
]],
['CR06', 'Security Considerations', [
['CR06:001', 'การเชื่อมต่อในสภาพแวดล้อมใช้งานจริงต้องเข้ารหัสด้วย HTTPS/TLS', 'NFR-001'],
['CR06:002', 'ค่าตั้งค่าและความลับของระบบต้องไม่ถูกเก็บใน Source Control และไม่เข้าถึงได้จากเว็บสาธารณะ', 'NFR-001'],
['CR06:003', 'การทำงานฝั่งเซิร์ฟเวอร์ต้องตรวจสอบข้อมูลนำเข้า ยืนยันตัวตน ตรวจสิทธิ์ และจำกัดขอบเขตบริษัท/คลัง', 'NFR-002'],
['CR06:004', 'ระบบต้องจำกัดสิทธิ์การเข้าถึงตามบทบาท (Role-based Access Control) ทั้งใน UI และฝั่งเซิร์ฟเวอร์', 'FR-002'],
['CR06:005', 'ระบบต้องป้องกัน SQL Injection และ Cross-Site Scripting', 'NFR-002'],
['CR06:006', 'ระบบต้องบล็อกการเข้าสู่ระบบซ้ำซ้อน (Concurrent Login) ของบัญชีเดียวกัน', 'FR-003'],
]],
['CR07', 'Environmental Considerations', [
['CR07:001', 'ระบบต้องทำงานบน Linux Server ในรูปแบบติดตั้งเอง (LAMP) หรือ Docker Compose', 'NFR-004'],
['CR07:002', 'ระบบต้องใช้เขตเวลา Asia/Bangkok อย่างสม่ำเสมอทั้งแอปพลิเคชันและงานตามกำหนดเวลา', 'NFR-010'],
['CR07:003', 'ระบบต้องใช้งานได้บนอุปกรณ์ Desktop และอุปกรณ์พกพาผ่าน Web Browser', 'NFR-005'],
['CR07:004', 'ระบบต้องแยกข้อมูลสาธิต/ทดสอบออกจากข้อมูลใช้งานจริงได้', 'Data requirements'],
]],
['CR08', 'Operational Considerations', [
['CR08:001', 'ระบบต้องมีการสำรองฐานข้อมูลอัตโนมัติรายวัน', 'NFR-004'],
['CR08:002', 'ระบบต้องกู้คืนข้อมูลจากชุดสำรองได้ตามขั้นตอนที่จัดทำเป็นเอกสาร', 'NFR-004'],
['CR08:003', 'ระบบต้องมีการเฝ้าระวังสถานะบริการ Web, Database และ Node.js พร้อมแจ้งเตือนเมื่อขัดข้อง', 'NFR-004'],
['CR08:004', 'ระบบต้องรองรับหลายบริษัท (Multi-company) และหลายคลังสินค้าภายใต้การแยกข้อมูล', 'FR-024'],
['CR08:005', 'งานตามกำหนดเวลาต้องทำงานสำเร็จโดยไม่สร้างผลลัพธ์ซ้ำหรือเกินสิทธิ์', 'FR-022'],
]],
['CR09', 'Maintenance Considerations', [
['CR09:001', 'ซอฟต์แวร์ต้องมีโครงสร้างแบบโมดูล (Manager Classes / API) เพื่อให้แก้ไขได้โดยไม่กระทบส่วนอื่น', 'NFR-007'],
['CR09:002', 'ระบบต้องมีแม่แบบค่าตั้งค่า (Configuration Template) และควบคุมเวอร์ชันด้วย Git', 'NFR-007'],
['CR09:003', 'ระบบต้องมีเอกสารคู่มือการบำรุงรักษาและประวัติข้อบกพร่องที่แก้ไขแล้ว', 'NFR-004'],
['CR09:004', 'การเปลี่ยนแปลงโครงสร้างฐานข้อมูลต้องสะท้อนใน setup.php และตาราง schema_migrations', 'NFR-007'],
]],
['CR10', 'Installation Considerations', [
['CR10:001', 'ระบบต้องติดตั้งฐานข้อมูลได้ในขั้นตอนเดียวผ่าน setup.php', 'NFR-004'],
['CR10:002', 'ระบบต้องติดตั้งแบบ Container ได้ด้วยคำสั่ง docker compose up -d --build', 'NFR-004'],
['CR10:003', 'ระบบต้องมีสคริปต์สร้างไฟล์ .env และค่าความลับอัตโนมัติ (docker/init-env.sh)', 'NFR-001'],
['CR10:004', 'ระบบต้องมีเอกสารขั้นตอนการติดตั้งและตั้งค่า', 'NFR-004'],
]],
['CR11', 'Support Considerations', [
['CR11:001', 'ต้องมีคู่มือผู้ใช้งาน (Software User Document)', 'Documentation'],
['CR11:002', 'ต้องมีคู่มือปฏิบัติงานสำหรับผู้ดูแลระบบ (Product Operation Guide)', 'Documentation'],
['CR11:003', 'ต้องมีการอบรมผู้ใช้งานก่อนเปิดใช้งานจริง', 'Training'],
['CR11:004', 'ต้องมีช่องทางสนับสนุนและระดับการให้บริการ (SLA) หลังส่งมอบ', 'Maintenance'],
]],
['CR12', 'Design Constraints', [
['CR12:001', 'ระบบต้องพัฒนาด้วย PHP, MariaDB และ Node.js ตามที่องค์กรอนุมัติ', 'NFR-008'],
['CR12:002', 'ระบบต้องควบคุม Source Code ด้วย Git โดยมี main เป็น Baseline หลัก', 'Configuration'],
['CR12:003', 'รหัสผ่านผู้ใช้ต้องเข้ารหัสด้วย bcrypt และไม่เก็บหรือบันทึกเป็นข้อความธรรมดา', 'NFR-002'],
['CR12:004', 'โครงการต้องจัดทำ Work Products ตามมาตรฐาน ISO/IEC 29110 Basic Profile', 'NFR-009'],
]],
['CR13', 'Safety and Reliability Considerations', [
['CR13:001', 'การเปลี่ยนแปลงฐานข้อมูลที่เกี่ยวข้องกัน (เอกสาร + สต๊อก + GL) ต้องเป็น Transaction เดียวกัน', 'NFR-003'],
['CR13:002', 'ระบบต้องป้องกันยอดสต๊อกติดลบและความเคลื่อนไหวซ้ำซ้อน', 'NFR-003'],
['CR13:003', 'ระบบต้องรองรับการสำรองและกู้คืน (Backup & Recovery) ทั้ง Source Code และฐานข้อมูล', 'NFR-004'],
['CR13:004', 'ระบบต้องจัดการข้อผิดพลาดโดยแสดงข้อความที่เข้าใจได้แทน Fatal Error', 'NFR-006'],
]],
['CR14', 'Quality Expectations', [
['CR14:001', 'ระบบต้องผ่านการทดสอบตาม Test Case ที่กำหนดก่อนส่งมอบ', 'Acceptance'],
['CR14:002', 'ทุกความต้องการต้องสอบกลับได้ถึงการออกแบบ ส่วนประกอบ และหลักฐานการทดสอบ', 'NFR-009'],
['CR14:003', 'ระบบต้องผ่านการทดสอบการยอมรับ (UAT) โดยตัวแทนลูกค้าบนสภาพแวดล้อมใช้งานจริง', 'Acceptance'],
['CR14:004', 'ต้องไม่มีข้อบกพร่องระดับวิกฤตค้างอยู่ในด้านความปลอดภัย การแยกข้อมูล และความถูกต้องของสต๊อก/บัญชี', 'Acceptance'],
]],
];
export const CRS = CR_GROUPS.flatMap(([, , rows]) => rows);
export const crTopic = (id) => CRS.find((r) => r[0] === id)?.[1] ?? '';
BIN
View File
Binary file not shown.

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