Files
wms-app/scripts/sdlc-seed/engineering.mjs
T
Thanakorn 087e7b3958 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.
2026-09-09 08:09:54 +07:00

304 lines
48 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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`);
}