[CLAUDE] Docs: User Manual 7 file rewrite compact cho end-user

User feedback: "ko cần quá đầy đủ chi tiết, cho end-user họ làm".

Rewrite 7 generator scripts theo style end-user friendly:
- Bỏ field validation table 5 cột (Tên field / Kiểu / Bắt buộc / Validation / Ví dụ)
- Bỏ error troubleshoot table 3 cột (Lỗi / Nguyên nhân / Cách xử lý)
- Bỏ FAQ chi tiết 8 câu (giữ 1 chương "Khi gặp lỗi" 4-5 bullet)
- Bỏ phím tắt table
- Giữ: tổng quan ngắn, numbered steps đơn giản, note/warn/tip chỉ khi critical

7 file generator (đã refactor dùng _helpers.js shared):
- _gen-user-01: Bắt đầu (login + đổi pwd + hồ sơ + dashboard + sidebar + chuông)
- _gen-user-02: Hợp đồng (7 loại + tạo + sửa + xóa + đính kèm + comment)
- _gen-user-03: Duyệt Workflow (9 phase + 2-stage NV/TPB + reject + resume)
- _gen-user-04: Phiếu Duyệt NCC (PE) (A/B + matrix báo giá + winner + 4PB + tạo HĐ)
- _gen-user-05: Ngân sách (tạo + hạng mục + WF 3-step + liên kết HĐ/PE)
- _gen-user-06: Cheatsheet 7 loại HĐ (mỗi loại 1 page: use case + field + format mã)
- _gen-admin-02: Quản lý Users-Roles (tạo + role + reset + lock + bypass review S9)

Setup: package.json + npm install docx@9.5.0 + script "gen:all".

Output sizes:
- 01: 12.1 KB (cũ 21.7 KB → giảm ~44%)
- 02: 12.4 KB
- 03: 12.2 KB
- 04: 12.4 KB
- 05: 12.0 KB
- 06: 12.8 KB
- admin-02: 12.7 KB
- Tổng ~86 KB cho 7 file đầy đủ chức năng cốt lõi.

Note: ContractType label "Phương án" → "Giải pháp" (đã rebrand session 3).
2-stage dept approval mention ở Phần 03 + admin-02 (Migration 16 Session 8/9).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-06 17:06:01 +07:00
parent bdd175c07b
commit 16c2c9c79e
17 changed files with 1231 additions and 0 deletions

View File

@ -0,0 +1,100 @@
// Admin Manual 02 — Quản lý Users + Roles (cho QTV)
// Style: end-user friendly — các bước quản trị cơ bản.
const fs = require('fs'); const path = require('path');
const H = require('./_helpers');
const { h1, h2, h3, p, bullet, num, note, warn, tip, titlePage, buildDoc, Packer } = H;
const children = [
...titlePage(
"Hướng dẫn Quản lý Users + Roles",
"Phần 02 (Admin): Tạo User, Gán Role, Reset Password, Khóa/Mở, Bypass Review",
),
h1("1. Truy cập trang Users"),
num("Đăng nhập admin tại https://admin.solutions.com.vn"),
num("Sidebar → Hệ thống → Users (hoặc URL /system/users)"),
num("Trang hiển thị danh sách user + filter + search"),
h1("2. Tạo User mới"),
num("Click \"Thêm user\" góc trên phải"),
num("Điền form: Email công ty / Họ tên / Phòng ban / Chức vụ / Password tạm"),
num("Tick các Vai trò gán cho user (1 user có thể có nhiều role)"),
num("Click \"Tạo\""),
note("Password tạm khuyến nghị: User@123456. User sẽ bị bắt đổi khi đăng nhập lần đầu."),
h1("3. Sửa thông tin User"),
num("Tại danh sách, click icon Pencil ở dòng user"),
num("Sửa Họ tên / Phòng ban / Chức vụ / Active"),
num("Click \"Lưu\""),
warn("Email là read-only — KHÔNG đổi được. Nếu cần đổi email, phải xóa user và tạo lại."),
h1("4. Gán / Sửa Roles"),
num("Click icon Shield ở dòng user"),
num("Tick / Bỏ tick các role"),
num("Click \"Lưu\""),
p("12 role hệ thống: Admin / Drafter / DeptManager (TPB) / ProjectManager / Procurement / CostControl / Finance / Accounting / Equipment / Director / AuthorizedSigner / HrAdmin."),
tip("User được gán Admin role → bypass mọi permission check (dùng cho test hoặc emergency)."),
h1("5. Reset Password"),
num("Click icon Key ở dòng user"),
num("Nhập password mới (≥ 6 ký tự)"),
num("Click \"Reset\""),
p("User bị logout khỏi mọi session, phải đăng nhập lại với password mới."),
warn("Báo password mới cho user qua kênh an toàn (gặp trực tiếp / cuộc gọi). KHÔNG gửi qua email không mã hóa."),
h1("6. Khóa / Mở khóa User"),
num("User bị khóa tự động khi đăng nhập sai 5 lần liên tiếp"),
num("Khi đó icon Lock màu cam hiện ở dòng user"),
num("Admin click icon Unlock để mở khóa thủ công"),
bullet("Hoặc đợi 30 phút auto unlock"),
h1("7. Vô hiệu hóa / Kích hoạt User"),
num("Click icon X (đỏ) hoặc CheckCircle (xanh) ở cột actions"),
num("Toggle trạng thái Active"),
p("User Inactive vẫn còn trong DB nhưng KHÔNG đăng nhập được. Dùng khi nhân viên nghỉ việc."),
h1("8. Bypass Review (mới — Migration 16)"),
p("Khi quy trình duyệt 2-cấp phòng ban, mặc định: NV duyệt = Review only, phải TPB Confirm thì phase mới chuyển. \"Bypass Review\" cho phép 1 NV cụ thể duyệt = Confirm trực tiếp (skip Review)."),
num("Tại danh sách user, click icon ShieldCheck ở dòng user"),
num("Icon đổi màu fuchsia + cột \"Bypass\" hiện badge \"bypass\""),
num("Lần duyệt sau, NV này được Stage = Confirm + IsBypassed = true (audit)"),
bullet("Use case: phòng ban không có TPB, hoặc TPB ủy quyền cho 1 NV cụ thể"),
bullet("Click lần nữa để TẮT bypass"),
h1("9. Tìm + Filter user"),
bullet("Search: tìm theo Email / Họ tên"),
bullet("Phân trang 20 user/page"),
tip("Bookmark URL nếu cần truy cập nhanh: /system/users?search=tra.bui"),
h1("10. Quản lý Roles"),
num("Sidebar → Hệ thống → Roles (URL /system/roles)"),
num("Xem 12 role hệ thống: Mã (ShortName) + Tên đầy đủ + Mô tả"),
num("(Tùy chọn) Tạo role custom ngoài 12 hardcoded — click \"Thêm role\""),
warn("Sửa role hệ thống có thể ảnh hưởng quy trình duyệt. Test kỹ trước khi đổi."),
h1("11. Quản lý Permissions"),
num("Sidebar → Hệ thống → Permissions (URL /system/permissions)"),
num("Trang 3-panel: Role list trái | Matrix Menu × CRUD giữa | Stats phải"),
num("Chọn 1 role → Matrix hiển thị quyền theo từng menu key"),
num("Tick / Bỏ tick CRUD flag → tự động lưu (auto-save)"),
note("User cần logout / login lại để thấy menu mới sau khi admin đổi quyền."),
h1("12. Khi gặp lỗi"),
bullet("Tạo user báo \"Email đã tồn tại\" → kiểm tra danh sách (kể cả Inactive)"),
bullet("Reset password không tác dụng → check user IsActive = true"),
bullet("User báo không thấy menu nào → kiểm tra Permissions matrix cho role tương ứng"),
bullet("Bypass Review không có hiệu lực → user phải có Phòng ban gán + role không phải DeptManager"),
];
const doc = buildDoc(
"Admin Manual - 02 Quản lý Users + Roles",
"SOLUTION_ERP - Admin Manual - 02 Quản lý Users-Roles",
children,
);
const outPath = path.join(__dirname, 'admin', '02-Quan-ly-Users-Roles.docx');
Packer.toBuffer(doc).then(buf => {
fs.writeFileSync(outPath, buf);
console.log('✅ Generated:', outPath);
console.log(' Size:', (buf.length / 1024).toFixed(1) + ' KB');
});

View File

@ -0,0 +1,83 @@
// User Manual 01 — Bắt đầu (Đăng nhập + Đổi pwd + Hồ sơ + Dashboard + Sidebar + Chuông)
// Style: end-user friendly — chỉ các bước thực hiện đơn giản, không bảng field/error chi tiết.
const fs = require('fs'); const path = require('path');
const H = require('./_helpers');
const { h1, h2, h3, p, bullet, num, note, warn, tip, titlePage, buildDoc, Packer } = H;
const children = [
...titlePage(
"Hướng dẫn Bắt đầu",
"Phần 01: Đăng nhập, Đổi mật khẩu, Hồ sơ, Điều hướng",
),
h1("1. Mở hệ thống"),
p("Truy cập https://eoffice.solutions.com.vn bằng trình duyệt Chrome / Edge / Cốc Cốc."),
p("Tài khoản: email công ty cấp (vd ten.ho@solutions.com.vn). Mật khẩu lần đầu: User@123456."),
h1("2. Đăng nhập"),
num("Mở https://eoffice.solutions.com.vn"),
num("Nhập Email + Mật khẩu"),
num("Click \"Đăng nhập\""),
note("Lần đầu đăng nhập, hệ thống tự chuyển sang trang Đổi mật khẩu."),
h1("3. Đổi mật khẩu lần đầu"),
num("Nhập Mật khẩu hiện tại (User@123456)"),
num("Nhập Mật khẩu mới (≥ 6 ký tự, có chữ hoa, chữ thường, số, ký tự đặc biệt)"),
num("Nhập lại Mật khẩu mới để xác nhận"),
num("Click \"Lưu\""),
num("Hệ thống đăng xuất → đăng nhập lại bằng mật khẩu mới"),
warn("Phải nhớ chính xác mật khẩu mới. Nếu quên, liên hệ admin reset."),
h1("4. Cập nhật hồ sơ cá nhân"),
num("Click avatar góc trên phải → \"Hồ sơ của tôi\""),
num("Sửa Họ tên / Chức vụ / Số điện thoại"),
num("Click \"Lưu\""),
note("Email + Phòng ban + Vai trò là read-only, do admin gán. Liên hệ admin nếu cần đổi."),
h1("5. Dashboard - Trang chủ"),
p("Sau khi đăng nhập, Dashboard hiển thị 5 thẻ:"),
bullet("Bản nháp đang soạn — HĐ user là Drafter, ở phase Đang soạn thảo"),
bullet("Chờ tôi duyệt — HĐ ở phase mà role user được duyệt"),
bullet("Sắp đến hạn — HĐ có SLA trong 2 ngày tới"),
bullet("Quá hạn — HĐ đã quá SLA chưa xử lý"),
bullet("Tổng giá trị nháp — tổng giá trị HĐ đang soạn"),
p("Click thẻ → mở danh sách tương ứng."),
h1("6. Sidebar - Menu trái"),
p("Menu 3 cấp:"),
num("Cấp 1: Hợp đồng / Phiếu duyệt NCC / Ngân sách / Báo cáo / ..."),
num("Cấp 2: 7 loại HĐ (Thầu phụ, Giao khoán, NCC, Dịch vụ, Mua bán, Nguyên tắc NCC, Nguyên tắc DV)"),
num("Cấp 3: 3 thao tác (Danh sách / Tạo mới / Duyệt)"),
tip("Click cấp 1 → mở rộng. Khi mở 1 nhóm, các nhóm khác tự đóng."),
h1("7. Chuông thông báo"),
p("Icon chuông góc trên phải. Badge số đỏ = số notification chưa đọc."),
num("Click chuông → popup 10 notification mới nhất"),
num("Click 1 notification → đánh dấu đã đọc + chuyển đến trang HĐ liên quan"),
num("Click \"Đánh dấu tất cả đã đọc\" để clear badge"),
note("Notification mới đẩy realtime — không cần refresh trang."),
h1("8. Đăng xuất"),
num("Click avatar góc trên phải"),
num("Click \"Đăng xuất\""),
p("Hệ thống clear session, chuyển về trang Đăng nhập."),
h1("9. Khi gặp lỗi"),
bullet("Sai mật khẩu nhiều lần → tài khoản bị khóa 30 phút (auto unlock)"),
bullet("Trang trắng / không response → Ctrl + Shift + R (hard refresh)"),
bullet("Sidebar trống → liên hệ admin xác nhận quyền"),
bullet("Lỗi không tự xử lý → email admin@solutions.com.vn kèm screenshot + URL"),
];
const doc = buildDoc(
"User Manual - 01 Bắt đầu",
"SOLUTION_ERP - User Manual - 01 Bắt đầu",
children,
);
const outPath = path.join(__dirname, 'user', '01-Bat-dau.docx');
Packer.toBuffer(doc).then(buf => {
fs.writeFileSync(outPath, buf);
console.log('✅ Generated:', outPath);
console.log(' Size:', (buf.length / 1024).toFixed(1) + ' KB');
});

View File

@ -0,0 +1,96 @@
// User Manual 02 — Hợp đồng (Tạo + Sửa + 7 loại + Xóa)
// Style: end-user friendly — các bước đơn giản.
const fs = require('fs'); const path = require('path');
const H = require('./_helpers');
const { h1, h2, h3, p, bullet, num, note, warn, tip, titlePage, buildDoc, Packer } = H;
const children = [
...titlePage(
"Hướng dẫn Hợp đồng",
"Phần 02: 7 loại HĐ - Tạo, Sửa, Xóa",
),
h1("1. 7 loại hợp đồng"),
bullet("HĐ Thầu phụ (HĐTP) — thuê NTP xây thô / hoàn thiện"),
bullet("HĐ Giao khoán (HĐGK) — khoán công việc cho tổ đội"),
bullet("HĐ NCC — mua vật tư, thiết bị từ NCC"),
bullet("HĐ Dịch vụ (HĐDV) — thuê tư vấn, kiểm định, vệ sinh"),
bullet("HĐ Mua bán (HĐMB) — mua bán thiết bị, vật phẩm"),
bullet("HĐ Nguyên tắc NCC — khung nguyên tắc lâu dài với NCC"),
bullet("HĐ Nguyên tắc DV — khung nguyên tắc dịch vụ lâu dài"),
note("Tham khảo Phần 06 để biết field đặc thù mỗi loại."),
h1("2. Inbox vs Hợp đồng của tôi"),
bullet("Inbox (/inbox) — HĐ đang chờ vai trò bạn duyệt"),
bullet("Hợp đồng của tôi (/my-contracts) — HĐ bạn là Drafter (mọi phase)"),
tip("Filter theo loại: thêm ?type=N (1-7) vào URL hoặc dùng menu Sidebar."),
h1("3. Tạo HĐ mới"),
num("Sidebar → Hợp đồng → chọn loại HĐ → Tạo mới"),
num("Hoặc URL trực tiếp /contracts/new?type=N (N = 1-7)"),
num("Điền Header: Tên HĐ, NCC/NTP, Dự án, Phòng ban chủ trì, Giá trị, Ngày dự kiến"),
num("(Tùy chọn) Liên kết Ngân sách dự án"),
num("Click \"Lưu nháp\" → hệ thống tạo HĐ ở phase Đang soạn thảo"),
note("Mã HĐ chưa gen ngay — sẽ tự gen khi chuyển sang phase Đang đóng dấu."),
h1("4. Thêm chi tiết HĐ"),
p("Sau khi tạo nháp, mở HĐ → tab \"Chi tiết\" để nhập hạng mục cụ thể."),
num("Mở HĐ → tab \"Chi tiết\""),
num("Click \"Thêm hạng mục\""),
num("Nhập Mã / Tên hạng mục / Đơn vị / Khối lượng / Đơn giá"),
num("Hệ thống tự tính Thành tiền = Khối lượng × Đơn giá"),
num("Click \"Lưu\""),
tip("Lặp lại để thêm nhiều hạng mục. Tổng giá trị HĐ tự tổng hợp."),
h1("5. Sửa HĐ nháp"),
num("Mở HĐ ở phase Đang soạn thảo"),
num("Click \"Sửa\" ở Header hoặc inline edit từng hạng mục"),
num("Click \"Lưu\""),
warn("Sau khi trình duyệt (Phase ≠ Đang soạn thảo), HĐ bị khóa edit. Phải reject về Đang soạn thảo mới sửa được."),
h1("6. Trình duyệt HĐ"),
num("Mở HĐ ở phase Đang soạn thảo"),
num("Panel Quy trình bên phải → click \"Trình → Đang góp ý\""),
num("(Tùy chọn) Nhập ghi chú"),
num("Click \"Xác nhận\""),
p("HĐ chuyển sang phase tiếp theo, người duyệt nhận notification."),
p("Chi tiết quy trình duyệt 9 phase: xem Phần 03 - Duyệt Workflow."),
h1("7. Đính kèm file"),
num("Mở HĐ → tab \"Đính kèm\" hoặc kéo thả file vào khu vực dropzone"),
num("Chọn loại file: Báo giá / Yêu cầu KT / Phiếu duyệt / ..."),
num("File upload xong, hiển thị trong danh sách"),
num("Click tên file để tải về"),
h1("8. Comment / Góp ý"),
num("Mở HĐ → tab \"Bình luận\""),
num("Nhập nội dung góp ý vào khung text"),
num("Click \"Gửi\""),
note("Drafter + role đã comment trước nhận notification realtime."),
h1("9. Xóa HĐ nháp"),
num("Mở HĐ ở phase Đang soạn thảo / Từ chối"),
num("Click icon \"Xóa\" (thùng rác) ở Header"),
num("Xác nhận \"Xóa\""),
warn("Chỉ xóa được HĐ ở phase Đang soạn thảo hoặc Từ chối. HĐ đã trình duyệt KHÔNG xóa được."),
h1("10. Tìm + Filter"),
bullet("Search box: tìm theo Mã HĐ / Tên / NCC"),
bullet("Filter Phase: dropdown 9 phase + Từ chối"),
bullet("Filter NCC / Dự án: dropdown"),
bullet("Filter SLA: chỉ hiện HĐ sắp đến hạn / quá hạn"),
tip("URL có thể bookmark, vd /my-contracts?type=5&phase=2 = HĐ Mua bán phase Đang soạn thảo."),
];
const doc = buildDoc(
"User Manual - 02 Hợp đồng",
"SOLUTION_ERP - User Manual - 02 Hợp đồng",
children,
);
const outPath = path.join(__dirname, 'user', '02-Hop-dong.docx');
Packer.toBuffer(doc).then(buf => {
fs.writeFileSync(outPath, buf);
console.log('✅ Generated:', outPath);
console.log(' Size:', (buf.length / 1024).toFixed(1) + ' KB');
});

View File

@ -0,0 +1,94 @@
// User Manual 03 — Duyệt Workflow (9 phase + 2-stage approval + smart reject)
// Style: end-user friendly — các bước duyệt cơ bản.
const fs = require('fs'); const path = require('path');
const H = require('./_helpers');
const { h1, h2, h3, p, bullet, num, note, warn, tip, titlePage, buildDoc, Packer } = H;
const children = [
...titlePage(
"Hướng dẫn Duyệt Workflow",
"Phần 03: 9 phase HĐ + 2-cấp duyệt phòng ban + Từ chối/Trình lại",
),
h1("1. 9 phase quy trình"),
num("Đang chọn — chưa khởi tạo"),
num("Đang soạn thảo — Drafter đang soạn"),
num("Đang góp ý — các phòng ban góp ý song song"),
num("Đang đàm phán — đàm phán với NCC/NTP"),
num("Đang in ký — in nháp, ký nháp"),
num("Đang kiểm tra CCM — CCM kiểm tra (skip với HĐ Bypass)"),
num("Đang trình ký — BOD trình ký"),
num("Đang đóng dấu — HRA đóng dấu (mã HĐ tự gen)"),
num("Đã phát hành — HĐ phát hành chính thức"),
bullet("Từ chối — bị reject, Drafter có thể sửa và trình lại"),
h1("2. Inbox - HĐ chờ tôi duyệt"),
num("Mở /inbox hoặc Sidebar → \"Hộp thư\""),
num("Panel trái: danh sách HĐ chờ vai trò bạn duyệt"),
num("Click 1 HĐ → Panel giữa hiện chi tiết, Panel phải hiện Quy trình"),
h1("3. Duyệt 1 HĐ"),
num("Mở HĐ trong Inbox"),
num("Đọc kỹ: Header / Chi tiết / Đính kèm / Bình luận"),
num("Panel Quy trình bên phải → click nút \"Trình → [phase tiếp theo]\""),
num("(Tùy chọn) Nhập ghi chú duyệt"),
num("Click \"Xác nhận\""),
p("HĐ chuyển sang phase mới, Drafter + role kế tiếp nhận notification."),
h1("4. Duyệt 2-cấp phòng ban (mới)"),
p("Khi vai trò bạn thuộc phòng ban (ví dụ NV.PRO), quy trình duyệt 2 cấp:"),
num("Bạn (NV) duyệt → ghi nhận \"Review\" — phase CHƯA chuyển"),
num("Trưởng phòng cùng dept (TPB) duyệt → ghi nhận \"Confirm\" — phase chuyển"),
p("Panel Quy trình hiển thị section \"Tiến trình duyệt 2-cấp phòng ban\":"),
bullet("Review NV — ai đã review + thời gian + ghi chú"),
bullet("Confirm TPB — chờ TPB confirm (highlight vàng)"),
note("Admin có thể bật \"Bypass\" cho 1 NV cụ thể — NV được Confirm trực tiếp, skip Review."),
h1("5. Từ chối (Reject)"),
num("Mở HĐ trong Inbox"),
num("Panel Quy trình → click nút \"Trình → Từ chối\""),
num("Nhập lý do từ chối (bắt buộc)"),
num("Click \"Xác nhận\""),
p("HĐ tự động về phase Đang soạn thảo. Drafter sửa rồi trình lại."),
h1("6. Trình lại sau khi sửa (Resume)"),
num("Drafter mở HĐ ở Đang soạn thảo (sau reject)"),
num("Sửa Header / Chi tiết theo góp ý"),
num("Panel Quy trình → click \"Trình → [phase tiếp theo]\""),
num("Click \"Xác nhận\""),
note("Hệ thống tự nhảy về phase đã reject (không cần đi lại từ đầu)."),
h1("7. Comment / Góp ý"),
num("Mở HĐ → tab \"Bình luận\""),
num("Nhập nội dung"),
num("Click \"Gửi\""),
p("Comment KHÔNG ảnh hưởng phase — chỉ thông tin cho team."),
h1("8. SLA - Hạn xử lý"),
bullet("Mỗi phase có SLA mặc định (1-7 ngày tùy phase)"),
bullet("Khi sắp hết hạn (còn 20%), Drafter nhận notification cảnh báo"),
bullet("Hết hạn → hệ thống tự auto-approve (qua phase tiếp theo)"),
tip("Inbox có badge đỏ \"Quá hạn\" để ưu tiên xử lý."),
h1("9. Lịch sử duyệt"),
num("Mở HĐ → Panel phải → cuộn xuống section \"Lịch sử duyệt\""),
p("Hiển thị từng lần chuyển phase: ai duyệt, từ phase nào → phase nào, ghi chú, thời gian."),
h1("10. Quy tắc đặc biệt"),
bullet("Admin có quyền duyệt mọi phase (skip role check + 2-stage)"),
bullet("HĐ Dịch vụ / Mua bán / Nguyên tắc bypass phase Kiểm tra CCM"),
bullet("Sau Đang đóng dấu, HĐ KHÔNG sửa được nữa — chỉ xem"),
];
const doc = buildDoc(
"User Manual - 03 Duyệt Workflow",
"SOLUTION_ERP - User Manual - 03 Duyệt Workflow",
children,
);
const outPath = path.join(__dirname, 'user', '03-Duyet-Workflow.docx');
Packer.toBuffer(doc).then(buf => {
fs.writeFileSync(outPath, buf);
console.log('✅ Generated:', outPath);
console.log(' Size:', (buf.length / 1024).toFixed(1) + ' KB');
});

View File

@ -0,0 +1,89 @@
// User Manual 04 — Phiếu Duyệt NCC (PE) tiền-HĐ
// Style: end-user friendly — các bước đơn giản.
const fs = require('fs'); const path = require('path');
const H = require('./_helpers');
const { h1, h2, h3, p, bullet, num, note, warn, tip, titlePage, buildDoc, Packer } = H;
const children = [
...titlePage(
"Hướng dẫn Phiếu Duyệt NCC",
"Phần 04: Phiếu so sánh NCC/Thầu phụ tiền-HĐ — 2 quy trình A/B",
),
h1("1. Phiếu Duyệt NCC là gì?"),
p("Phiếu Duyệt NCC (PE) là biểu mẫu so sánh giá / năng lực giữa nhiều NCC trước khi tạo Hợp đồng chính thức."),
bullet("2 loại: \"Duyệt NCC\" (đơn giản) và \"Duyệt NCC + Giải pháp\" (có thêm bảng so sánh giải pháp kỹ thuật)"),
bullet("Khi phiếu \"Đã duyệt\" → có thể sinh HĐ 1-click với NCC thắng thầu được chọn"),
h1("2. Tạo phiếu mới"),
num("Sidebar → \"Phiếu Duyệt NCC\" → chọn loại → \"Tạo mới\""),
num("Hoặc URL /purchase-evaluations/new?type=1 (1 = NCC) hoặc ?type=2 (NCC + Giải pháp)"),
num("Điền Header: Tên gói thầu, Dự án, Phòng ban, (tùy chọn) Liên kết Ngân sách"),
num("Click \"Lưu nháp\""),
note("Mã phiếu format PE/{Năm}/{A|B}/{số thứ tự} — tự gen ngay khi tạo."),
h1("3. Thêm NCC tham gia"),
num("Mở phiếu → section \"NCC/Thầu phụ tham gia\""),
num("Click \"Thêm NCC\""),
num("Chọn NCC từ danh sách (hoặc tạo NCC mới qua Master)"),
num("Nhập điều khoản thanh toán + ghi chú (tùy chọn)"),
num("Click \"Lưu\""),
tip("Lặp lại để thêm 2-3 NCC so sánh. Tối thiểu 1 NCC, khuyến nghị 3 để chọn lựa."),
h1("4. Thêm hạng mục + báo giá"),
num("Mở phiếu → section \"Hạng mục + Báo giá\""),
num("Click \"Thêm hạng mục\" (Mã / Tên / Đơn vị / Khối lượng ngân sách + Khối lượng thi công)"),
num("Tại bảng matrix, mỗi NCC có 1 cột — nhập đơn giá báo giá NCC"),
num("Hệ thống tự tính Thành tiền = Khối lượng × Đơn giá"),
num("Click \"Lưu\""),
tip("Cột \"NS link · Δ\" so sánh đơn giá báo giá vs ngân sách (xanh = dưới NS, đỏ = vượt)."),
h1("5. Đính kèm báo giá NCC"),
num("Mỗi dòng NCC có nút \"Tải lên\" → đính kèm file báo giá / hồ sơ năng lực"),
num("Hoặc tab \"Đính kèm\" → upload file chung (Bảng so sánh tổng, Yêu cầu KT, ...)"),
h1("6. Chọn NCC thắng thầu"),
num("Sau khi nhập đủ báo giá, mở section \"Hạng mục + Báo giá\""),
num("Tại mỗi dòng hạng mục, tick checkbox cột NCC bạn chọn"),
num("Hoặc click \"Chọn NCC này\" để chọn cho toàn bộ hạng mục"),
num("Tổng giá trị NCC thắng tự cập nhật"),
h1("7. Ý kiến 4 phòng ban"),
p("Section \"Ý kiến 4 phòng ban\" có 4 ô: Phê duyệt / P.CCM / P.Mua hàng / SM-PM. Mỗi phòng ban điền ý kiến và ký."),
num("Trưởng phòng từng phòng ban mở phiếu"),
num("Tìm ô tương ứng phòng mình → nhập nội dung ý kiến"),
num("Click \"Lưu & Ký\" → ghi nhận chữ ký + thời gian"),
h1("8. Trình duyệt phiếu"),
num("Mở phiếu ở phase Đang soạn thảo"),
num("Panel Quy trình bên phải → click \"Trình → [phase tiếp theo]\""),
num("(Tùy chọn) Nhập ghi chú"),
num("Click \"Xác nhận\""),
p("Phiếu đi qua các phase tùy quy trình A (3 step: ChoPurchasing → ChoCCM → ChoCEONcc) hoặc B (5 step thêm ChoDuAn + ChoCEOPa)."),
note("Quy trình PE cũng có duyệt 2-cấp phòng ban (NV Review → TPB Confirm) giống HĐ — xem Phần 03."),
h1("9. Tạo HĐ từ phiếu Đã duyệt"),
num("Mở phiếu ở phase \"Đã duyệt\" và đã chọn NCC thắng thầu"),
num("Click \"Tạo HĐ từ phiếu\" (góc phải Header)"),
num("Chọn loại HĐ (1-7) muốn tạo"),
num("Click \"Tạo\""),
p("Hệ thống tạo HĐ nháp tự động: copy NCC + Dự án + (tùy chọn) Ngân sách. Drafter mở HĐ để điền chi tiết tiếp."),
h1("10. Sửa / Xóa phiếu nháp"),
bullet("Phase Đang soạn thảo: sửa Header / NCC / hạng mục / báo giá tự do"),
bullet("Phase ≠ Đang soạn thảo: bị khóa edit (tương tự HĐ)"),
bullet("Xóa: chỉ ở phase Đang soạn thảo / Từ chối, click icon thùng rác"),
];
const doc = buildDoc(
"User Manual - 04 Phiếu Duyệt NCC",
"SOLUTION_ERP - User Manual - 04 PE Phiếu Duyệt NCC",
children,
);
const outPath = path.join(__dirname, 'user', '04-PE-Phieu-Duyet-NCC.docx');
Packer.toBuffer(doc).then(buf => {
fs.writeFileSync(outPath, buf);
console.log('✅ Generated:', outPath);
console.log(' Size:', (buf.length / 1024).toFixed(1) + ' KB');
});

View File

@ -0,0 +1,83 @@
// User Manual 05 — Budget Ngân sách dự án
// Style: end-user friendly — các bước đơn giản.
const fs = require('fs'); const path = require('path');
const H = require('./_helpers');
const { h1, h2, h3, p, bullet, num, note, warn, tip, titlePage, buildDoc, Packer } = H;
const children = [
...titlePage(
"Hướng dẫn Ngân sách Dự án",
"Phần 05: Budget - Tạo Ngân sách + Hạng mục + Liên kết HĐ/PE",
),
h1("1. Ngân sách dự án là gì?"),
p("Module Ngân sách quản lý dự toán chi phí cho từng dự án. Mỗi dự án có thể có 1 hoặc nhiều ngân sách (theo năm hoặc theo hạng mục lớn)."),
bullet("HĐ + Phiếu PE có thể liên kết Ngân sách để theo dõi \"đã chi vs ngân sách\""),
bullet("Workflow đơn giản 3 bước: Đang soạn thảo → Chờ CCM → Chờ CEO → Đã duyệt"),
h1("2. Tạo ngân sách mới"),
num("Sidebar → \"Ngân sách\" → \"Tạo mới\""),
num("Hoặc URL /budgets/new"),
num("Điền Header: Tên ngân sách, Năm, Dự án, Phòng ban, Mô tả"),
num("Click \"Lưu nháp\""),
note("Mã ngân sách format NS-{Năm tháng}-{số thứ tự} — tự gen."),
h1("3. Thêm hạng mục ngân sách"),
num("Mở ngân sách → section \"Hạng mục\""),
num("Click \"Thêm hạng mục\""),
num("Nhập: Mã nhóm / Tên nhóm / Mã hạng mục / Nội dung / Đơn vị / Khối lượng / Đơn giá"),
num("Hệ thống tự tính Thành tiền = Khối lượng × Đơn giá"),
num("Tổng ngân sách tự tổng hợp ở Header"),
num("Click \"Lưu\""),
tip("Mã nhóm + Mã hạng mục dùng để match với chi tiết HĐ/PE khi đối chiếu \"đã chi vs ngân sách\"."),
h1("4. Sửa hạng mục"),
num("Mở ngân sách ở phase Đang soạn thảo"),
num("Click vào ô cần sửa → inline edit"),
num("Click ngoài để lưu (auto-save)"),
warn("Sau khi trình duyệt, ngân sách bị khóa edit. Phải reject về Đang soạn thảo mới sửa được."),
h1("5. Trình duyệt ngân sách"),
num("Mở ngân sách ở phase Đang soạn thảo"),
num("Panel Quy trình bên phải → click \"Trình → Chờ CCM\""),
num("CCM duyệt → \"Chờ CEO\""),
num("CEO duyệt → \"Đã duyệt\""),
note("Workflow Budget cũng có duyệt 2-cấp phòng ban (NV Review → TPB Confirm) — xem Phần 03."),
h1("6. Liên kết HĐ / Phiếu PE với ngân sách"),
p("Khi tạo HĐ hoặc Phiếu PE, chọn ngân sách trong ô \"Liên kết Ngân sách\":"),
num("Tạo HĐ mới → form Header có dropdown \"Ngân sách\""),
num("Filter chỉ hiện ngân sách: Phase = Đã duyệt + cùng Dự án"),
num("Chọn ngân sách → HĐ liên kết với ngân sách đó"),
h1("7. Cột \"So với ngân sách\" trong Phiếu PE"),
p("Khi PE có liên kết ngân sách, bảng Hạng mục có thêm cột \"NS link · Δ\":"),
bullet("Match dòng PE với ngân sách qua key \"Mã nhóm | Mã hạng mục\""),
bullet("Hiển thị Đơn giá ngân sách + Δ chênh lệch"),
bullet("Δ < 0 (xanh) — báo giá NCC dưới ngân sách (tốt)"),
bullet("Δ > 0 (đỏ) — báo giá vượt ngân sách (cảnh báo)"),
bullet("Δ = 0 (xám) — khớp ngân sách"),
h1("8. Xem changelog ngân sách"),
num("Mở ngân sách → Panel phải → section \"Lịch sử thay đổi\""),
p("Ghi nhận: ai sửa, sửa gì (Header / hạng mục / phase transition), thời gian."),
h1("9. Xóa ngân sách nháp"),
num("Mở ngân sách ở phase Đang soạn thảo / Từ chối"),
num("Click icon thùng rác ở Header"),
num("Xác nhận \"Xóa\""),
warn("Chỉ xóa được ngân sách ở phase Đang soạn thảo / Từ chối. Đã duyệt KHÔNG xóa được."),
];
const doc = buildDoc(
"User Manual - 05 Ngân sách",
"SOLUTION_ERP - User Manual - 05 Budget Ngân sách",
children,
);
const outPath = path.join(__dirname, 'user', '05-Budget-Ngan-sach.docx');
Packer.toBuffer(doc).then(buf => {
fs.writeFileSync(outPath, buf);
console.log('✅ Generated:', outPath);
console.log(' Size:', (buf.length / 1024).toFixed(1) + ' KB');
});

View File

@ -0,0 +1,168 @@
// User Manual 06 — Cheatsheet 7 loại HĐ (mỗi loại 1 page tóm gọn)
// Style: end-user friendly — chỉ thông tin cốt lõi để Drafter biết khi nào dùng loại nào.
const fs = require('fs'); const path = require('path');
const H = require('./_helpers');
const { h1, h2, p, bullet, num, note, tip, titlePage, buildDoc, Packer } = H;
const cheatsheet = (info) => {
const out = [
h1(info.title),
p("Khi nào dùng: " + info.use),
];
if (info.bypass !== undefined) {
out.push(p("Bypass CCM: " + (info.bypass ? "Có (skip phase Kiểm tra CCM)" : "Không (đầy đủ 9 phase)")));
}
out.push(h2("Field đặc thù khi tạo"));
for (const f of info.fields) out.push(bullet(f));
out.push(h2("Mã HĐ format"));
out.push(p(info.codeFormat));
if (info.tips) {
out.push(h2("Lưu ý"));
for (const t of info.tips) out.push(bullet(t));
}
return out;
};
const children = [
...titlePage(
"Cheatsheet 7 loại Hợp đồng",
"Phần 06: Khi nào dùng loại nào + Field đặc thù mỗi loại",
),
h1("Tóm tắt - Bảng phân loại nhanh"),
bullet("HĐ Thầu phụ (HĐTP) — thuê NTP làm việc cho dự án xây dựng (xây thô, hoàn thiện, MEP)"),
bullet("HĐ Giao khoán (HĐGK) — khoán việc cho tổ đội nhỏ (theo công nhật, theo m²)"),
bullet("HĐ NCC — mua vật tư / thiết bị 1 lần từ NCC (cụ thể đầu việc)"),
bullet("HĐ Dịch vụ (HĐDV) — thuê tư vấn / kiểm định / vệ sinh / dịch vụ phụ trợ"),
bullet("HĐ Mua bán (HĐMB) — mua bán thiết bị / hàng hóa (không phải vật tư xây dựng)"),
bullet("HĐ Nguyên tắc NCC — khung hợp tác lâu dài với NCC, cụ thể từng đợt qua phiếu yêu cầu"),
bullet("HĐ Nguyên tắc DV — khung dịch vụ lâu dài (vd hợp đồng vệ sinh năm)"),
tip("Nếu phân vân, hỏi P.CCM hoặc P.Mua hàng để chốt loại."),
...cheatsheet({
title: "1. HĐ Thầu phụ (HĐTP) — Type 1",
use: "Thuê nhà thầu phụ (NTP) thực hiện gói thầu xây dựng cho 1 dự án cụ thể.",
bypass: false,
fields: [
"NCC/NTP: chọn từ danh sách nhà thầu phụ (Master)",
"Dự án: chọn dự án áp dụng",
"Hạng mục: liệt kê đầy đủ công việc + khối lượng + đơn giá",
"Thanh toán: tỷ lệ tạm ứng / thanh toán theo tiến độ / quyết toán / bảo hành",
"Bảo hành: thời gian bảo hành sau quyết toán",
],
codeFormat: "{Mã DA}/HĐTP/SOL&{Mã NCC}/{số thứ tự 2 chữ số}",
tips: [
"Đầy đủ 9 phase, có duyệt CCM",
"Mỗi NTP chỉ 1 HĐTP active per dự án (chia nhiều đợt qua Hạng mục)",
],
}),
...cheatsheet({
title: "2. HĐ Giao khoán (HĐGK) — Type 2",
use: "Khoán việc cho tổ đội nhỏ (10-50 người) làm phần việc cụ thể trong dự án.",
bypass: false,
fields: [
"Tổ đội: chọn tổ trưởng + tên tổ (Master)",
"Dự án + Phạm vi công việc",
"Khối lượng giao khoán: theo m², công nhật, hoặc trọn gói",
"Đơn giá khoán: per đơn vị",
"Thời hạn hoàn thành",
],
codeFormat: "{Mã DA}/HĐGK/SOL&{Mã NCC}/{số thứ tự 2 chữ số}",
}),
...cheatsheet({
title: "3. HĐ NCC — Type 3",
use: "Mua vật tư / thiết bị 1 lần từ NCC cho 1 dự án (vd mua thép, xi măng, thang máy).",
bypass: false,
fields: [
"NCC: chọn từ Master",
"Dự án",
"Hạng mục: vật tư + quy cách + khối lượng + đơn giá",
"Giao hàng: địa điểm + thời gian + điều kiện",
"Thanh toán: tỷ lệ tạm ứng + còn lại sau giao hàng",
],
codeFormat: "{Mã DA}/NCC/SOL&{Mã NCC}/{số thứ tự 2 chữ số}",
tips: [
"Trước khi tạo HĐ NCC, NÊN tạo Phiếu Duyệt NCC (PE) để so sánh giá nhiều NCC",
"Sau khi PE Đã duyệt, click \"Tạo HĐ từ phiếu\" → tự sinh HĐ NCC nháp",
],
}),
...cheatsheet({
title: "4. HĐ Dịch vụ (HĐDV) — Type 4",
use: "Thuê dịch vụ tư vấn, kiểm định, thí nghiệm, vệ sinh, an ninh cho 1 dự án.",
bypass: true,
fields: [
"NCC dịch vụ",
"Dự án",
"Phạm vi dịch vụ: liệt kê công việc tư vấn / kiểm định / ...",
"Phí dịch vụ: trọn gói hoặc theo giờ / theo lần",
"Sản phẩm bàn giao: báo cáo / chứng nhận / ...",
],
codeFormat: "{Mã DA}/HĐDV/SOL&{Mã NCC}/{số thứ tự 2 chữ số}",
}),
...cheatsheet({
title: "5. HĐ Mua bán (HĐMB) — Type 5",
use: "Mua bán thiết bị / hàng hóa không thuộc nhóm vật tư xây dựng (vd máy tính văn phòng).",
bypass: true,
fields: [
"NCC bán hàng",
"Dự án (nếu có) hoặc Phòng ban đặt mua",
"Hạng mục hàng hóa: tên / model / số lượng / đơn giá",
"Bảo hành nhà sản xuất + bảo hành NCC",
"Phương thức giao hàng + thanh toán",
],
codeFormat: "{Mã DA}/MB/SOL&{Mã NCC}/{số thứ tự 2 chữ số}",
}),
...cheatsheet({
title: "6. HĐ Nguyên tắc NCC — Type 6",
use: "Khung hợp tác lâu dài với 1 NCC (vd hợp đồng nguyên tắc cung cấp xi măng cả năm).",
bypass: true,
fields: [
"NCC + Phạm vi sản phẩm",
"Đơn giá khung (có thể điều chỉnh theo CPI / thị trường)",
"Thời hạn HĐ nguyên tắc (thường 1 năm)",
"Điều kiện chung: thanh toán / giao hàng / phạt",
],
codeFormat: "{Năm 4 chữ số}/NCC/SOL&{Mã NCC}/{số thứ tự 2 chữ số}",
tips: [
"Mã HĐ theo NĂM (không theo dự án) — vì áp dụng cho nhiều dự án",
"Mỗi đợt mua thực tế tạo Phiếu yêu cầu vật tư (chưa có module — manual)",
],
}),
...cheatsheet({
title: "7. HĐ Nguyên tắc DV — Type 7",
use: "Khung dịch vụ lâu dài (vd hợp đồng vệ sinh hàng năm cho mọi dự án).",
bypass: true,
fields: [
"NCC dịch vụ + Phạm vi dịch vụ",
"Phí dịch vụ khung",
"Thời hạn HĐ nguyên tắc (thường 1 năm)",
"Điều kiện chung",
],
codeFormat: "{Năm 4 chữ số}/HĐDV/SOL&{Mã NCC}/{số thứ tự 2 chữ số}",
}),
h1("Mẹo chung khi tạo HĐ"),
bullet("Chọn đúng loại ngay từ đầu — không chuyển loại sau khi tạo (phải xóa + tạo lại)"),
bullet("Liên kết Ngân sách dự án nếu có — để theo dõi \"đã chi vs ngân sách\""),
bullet("Đính kèm đầy đủ: báo giá, biên bản đàm phán, phụ lục"),
bullet("Mã HĐ KHÔNG gen ngay — chỉ gen khi chuyển sang phase \"Đang đóng dấu\""),
];
const doc = buildDoc(
"User Manual - 06 Cheatsheet 7 loại HĐ",
"SOLUTION_ERP - User Manual - 06 Cheatsheet 7 loại HĐ",
children,
);
const outPath = path.join(__dirname, 'user', '06-7-Loai-HD-Cheatsheet.docx');
Packer.toBuffer(doc).then(buf => {
fs.writeFileSync(outPath, buf);
console.log('✅ Generated:', outPath);
console.log(' Size:', (buf.length / 1024).toFixed(1) + ' KB');
});

View File

@ -0,0 +1,292 @@
// Shared helpers cho 5 file user manual (02-06)
const {
Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
Header, Footer, AlignmentType, LevelFormat,
HeadingLevel, BorderStyle, WidthType, ShadingType, PageNumber,
TableOfContents, TabStopType, TabStopPosition,
} = require('docx');
const PAGE_WIDTH = 12240, PAGE_HEIGHT = 15840, MARGIN = 1440;
const CONTENT_WIDTH = PAGE_WIDTH - MARGIN * 2; // 9360
const FONT = "Calibri";
const BRAND = "1F7DC1";
// ===== Heading helpers =====
const h1 = (text) => new Paragraph({
heading: HeadingLevel.HEADING_1,
children: [new TextRun({ text, font: FONT, size: 32, bold: true, color: BRAND })],
spacing: { before: 360, after: 200 },
});
const h2 = (text) => new Paragraph({
heading: HeadingLevel.HEADING_2,
children: [new TextRun({ text, font: FONT, size: 26, bold: true, color: "333333" })],
spacing: { before: 280, after: 160 },
});
const h3 = (text) => new Paragraph({
heading: HeadingLevel.HEADING_3,
children: [new TextRun({ text, font: FONT, size: 22, bold: true, color: "555555" })],
spacing: { before: 200, after: 120 },
});
// ===== Text helpers =====
const p = (text, opts = {}) => new Paragraph({
children: [new TextRun({ text, font: FONT, size: 22, ...(opts.run || {}) })],
spacing: { after: 120 },
...opts,
});
const bullet = (text, level = 0) => new Paragraph({
numbering: { reference: "bullets", level },
children: [new TextRun({ text, font: FONT, size: 22 })],
spacing: { after: 80 },
});
const num = (text, level = 0) => new Paragraph({
numbering: { reference: "numbers", level },
children: [new TextRun({ text, font: FONT, size: 22 })],
spacing: { after: 80 },
});
// ===== Callout boxes =====
const note = (text) => new Paragraph({
shading: { fill: "FFF4D6", type: ShadingType.CLEAR },
border: { left: { style: BorderStyle.SINGLE, size: 18, color: "F0B429" } },
children: [new TextRun({ text: "💡 " + text, font: FONT, size: 22, italics: true })],
spacing: { before: 120, after: 120 },
indent: { left: 240 },
});
const warn = (text) => new Paragraph({
shading: { fill: "FFE5E5", type: ShadingType.CLEAR },
border: { left: { style: BorderStyle.SINGLE, size: 18, color: "D63031" } },
children: [new TextRun({ text: "⚠️ " + text, font: FONT, size: 22, italics: true })],
spacing: { before: 120, after: 120 },
indent: { left: 240 },
});
const tip = (text) => new Paragraph({
shading: { fill: "E8F5E9", type: ShadingType.CLEAR },
border: { left: { style: BorderStyle.SINGLE, size: 18, color: "4CAF50" } },
children: [new TextRun({ text: "✓ " + text, font: FONT, size: 22, italics: true })],
spacing: { before: 120, after: 120 },
indent: { left: 240 },
});
const code = (text) => new Paragraph({
shading: { fill: "F5F5F5", type: ShadingType.CLEAR },
children: [new TextRun({ text, font: "Consolas", size: 20 })],
spacing: { before: 80, after: 120 },
indent: { left: 240 },
});
// ===== Mock screenshot placeholder =====
const mockScreenshot = (label) => new Paragraph({
alignment: AlignmentType.CENTER,
shading: { fill: "FAFAFA", type: ShadingType.CLEAR },
border: {
top: { style: BorderStyle.DASHED, size: 6, color: "999999" },
bottom: { style: BorderStyle.DASHED, size: 6, color: "999999" },
left: { style: BorderStyle.DASHED, size: 6, color: "999999" },
right: { style: BorderStyle.DASHED, size: 6, color: "999999" },
},
spacing: { before: 200, after: 200 },
children: [new TextRun({
text: `[ Ảnh chụp màn hình: ${label} ]`,
font: FONT, size: 20, italics: true, color: "999999",
})],
});
// ===== Table cell builder =====
const border = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" };
const borders = { top: border, bottom: border, left: border, right: border };
const cellMargins = { top: 80, bottom: 80, left: 120, right: 120 };
const cell = (text, opts = {}) => new TableCell({
borders,
width: { size: opts.width, type: WidthType.DXA },
shading: opts.shading ? { fill: opts.shading, type: ShadingType.CLEAR } : undefined,
margins: cellMargins,
children: [new Paragraph({
children: [new TextRun({
text, font: FONT, size: 20,
bold: opts.bold || false,
color: opts.color || "000000",
})],
})],
});
// ===== Bảng nhập liệu chuẩn 5 cột =====
const fieldTable = (rows) => {
const widths = [2200, 1500, 1100, 2860, 1700];
return new Table({
width: { size: CONTENT_WIDTH, type: WidthType.DXA },
columnWidths: widths,
rows: [
new TableRow({ tableHeader: true, children: [
cell("Tên field", { width: widths[0], bold: true, shading: BRAND, color: "FFFFFF" }),
cell("Kiểu dữ liệu", { width: widths[1], bold: true, shading: BRAND, color: "FFFFFF" }),
cell("Bắt buộc", { width: widths[2], bold: true, shading: BRAND, color: "FFFFFF" }),
cell("Quy tắc validation", { width: widths[3], bold: true, shading: BRAND, color: "FFFFFF" }),
cell("Ví dụ", { width: widths[4], bold: true, shading: BRAND, color: "FFFFFF" }),
]}),
...rows.map(r => new TableRow({ children: [
cell(r[0], { width: widths[0], bold: true }),
cell(r[1], { width: widths[1] }),
cell(r[2], { width: widths[2], color: r[2] === "Có" ? "D63031" : "999999", bold: r[2] === "Có" }),
cell(r[3], { width: widths[3] }),
cell(r[4], { width: widths[4] }),
]})),
],
});
};
// ===== Bảng lỗi 3 cột =====
const errorTable = (rows) => {
const widths = [2800, 3360, 3200];
return new Table({
width: { size: CONTENT_WIDTH, type: WidthType.DXA },
columnWidths: widths,
rows: [
new TableRow({ tableHeader: true, children: [
cell("Lỗi / Triệu chứng", { width: widths[0], bold: true, shading: "D63031", color: "FFFFFF" }),
cell("Nguyên nhân", { width: widths[1], bold: true, shading: "D63031", color: "FFFFFF" }),
cell("Cách xử lý", { width: widths[2], bold: true, shading: "D63031", color: "FFFFFF" }),
]}),
...rows.map(r => new TableRow({ children: [
cell(r[0], { width: widths[0], bold: true }),
cell(r[1], { width: widths[1] }),
cell(r[2], { width: widths[2] }),
]})),
],
});
};
// ===== Bảng tổng quát N cột =====
const genericTable = (headers, rows, widths, headerColor = BRAND) => {
return new Table({
width: { size: CONTENT_WIDTH, type: WidthType.DXA },
columnWidths: widths,
rows: [
new TableRow({ tableHeader: true, children: headers.map((h, i) =>
cell(h, { width: widths[i], bold: true, shading: headerColor, color: "FFFFFF" })
)}),
...rows.map(r => new TableRow({ children: r.map((c, i) =>
cell(typeof c === 'string' ? c : c.text, {
width: widths[i],
bold: typeof c === 'object' ? c.bold : (i === 0),
})
)})),
],
});
};
// ===== Title page =====
const titlePage = (subtitle, partTitle) => [
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 1200, after: 300 },
children: [new TextRun({ text: "SOLUTION_ERP", font: FONT, size: 56, bold: true, color: BRAND })],
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 120 },
children: [new TextRun({
text: "Hướng dẫn Sử dụng - User Manual",
font: FONT, size: 36, bold: true, color: "333333",
})],
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 600 },
children: [new TextRun({
text: partTitle,
font: FONT, size: 28, color: "666666",
})],
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 1200 },
children: [new TextRun({
text: "Công ty TNHH Xây dựng Solutions | Hệ thống Quản lý Hợp đồng",
font: FONT, size: 22, italics: true, color: "888888",
})],
}),
new Paragraph({ children: [new TextRun("")], pageBreakBefore: true }),
h1("Mục lục"),
new TableOfContents("Table of Contents", { hyperlink: true, headingStyleRange: "1-3" }),
new Paragraph({ children: [new TextRun("")], pageBreakBefore: true }),
];
// ===== Document builder =====
const buildDoc = (title, headerLabel, contentChildren) => new Document({
creator: "SOLUTION_ERP",
title,
description: title,
styles: {
default: { document: { run: { font: FONT, size: 22 } } },
paragraphStyles: [
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 32, bold: true, font: FONT, color: BRAND },
paragraph: { spacing: { before: 360, after: 200 }, outlineLevel: 0 } },
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 26, bold: true, font: FONT, color: "333333" },
paragraph: { spacing: { before: 280, after: 160 }, outlineLevel: 1 } },
{ id: "Heading3", name: "Heading 3", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 22, bold: true, font: FONT, color: "555555" },
paragraph: { spacing: { before: 200, after: 120 }, outlineLevel: 2 } },
],
},
numbering: {
config: [
{ reference: "bullets",
levels: [
{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } } },
{ level: 1, format: LevelFormat.BULLET, text: "◦", alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 1440, hanging: 360 } } } },
] },
{ reference: "numbers",
levels: [
{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } } },
{ level: 1, format: LevelFormat.LOWER_LETTER, text: "%2)", alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 1440, hanging: 360 } } } },
] },
],
},
sections: [{
properties: {
page: {
size: { width: PAGE_WIDTH, height: PAGE_HEIGHT },
margin: { top: MARGIN, right: MARGIN, bottom: MARGIN, left: MARGIN },
},
},
headers: {
default: new Header({ children: [new Paragraph({
alignment: AlignmentType.RIGHT,
children: [new TextRun({
text: headerLabel,
font: FONT, size: 18, color: "888888",
})],
})]}),
},
footers: {
default: new Footer({ children: [new Paragraph({
tabStops: [{ type: TabStopType.RIGHT, position: TabStopPosition.MAX }],
children: [
new TextRun({ text: "© Solutions | v1.0 | 2026-05-04", font: FONT, size: 18, color: "888888" }),
new TextRun({ text: "\tTrang ", font: FONT, size: 18, color: "888888" }),
new TextRun({ children: [PageNumber.CURRENT], font: FONT, size: 18, color: "888888" }),
new TextRun({ text: " / ", font: FONT, size: 18, color: "888888" }),
new TextRun({ children: [PageNumber.TOTAL_PAGES], font: FONT, size: 18, color: "888888" }),
],
})]}),
},
children: contentChildren,
}],
});
module.exports = {
// Constants
PAGE_WIDTH, PAGE_HEIGHT, MARGIN, CONTENT_WIDTH, FONT, BRAND,
// Helpers
h1, h2, h3, p, bullet, num, note, warn, tip, code, mockScreenshot,
cell, fieldTable, errorTable, genericTable,
titlePage, buildDoc,
// Re-export docx primitives
Paragraph, TextRun, Packer,
};

Binary file not shown.

207
docs/_user-guide/package-lock.json generated Normal file
View File

@ -0,0 +1,207 @@
{
"name": "solution-erp-user-guide-generator",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "solution-erp-user-guide-generator",
"version": "1.0.0",
"dependencies": {
"docx": "9.5.0"
}
},
"node_modules/@types/node": {
"version": "22.19.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
"integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/docx": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/docx/-/docx-9.5.0.tgz",
"integrity": "sha512-WZggg9vVujFcTyyzfIVBBIxlCk51QvhLWl87wtI2zuBdz8C8C0mpRhEVwA2DZd7dXyY0AVejcEVDT9vn7Xm9FA==",
"license": "MIT",
"dependencies": {
"@types/node": "^22.7.5",
"hash.js": "^1.1.7",
"jszip": "^3.10.1",
"nanoid": "^5.1.3",
"xml": "^1.0.1",
"xml-js": "^1.6.8"
},
"engines": {
"node": ">=10"
}
},
"node_modules/hash.js": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"license": "ISC"
},
"node_modules/nanoid": {
"version": "5.1.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.11.tgz",
"integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^18 || >=20"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/sax": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
}
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/xml": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
"license": "MIT"
},
"node_modules/xml-js": {
"version": "1.6.11",
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
"integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
"license": "MIT",
"dependencies": {
"sax": "^1.2.4"
},
"bin": {
"xml-js": "bin/cli.js"
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"name": "solution-erp-user-guide-generator",
"version": "1.0.0",
"description": "Generate end-user manual .docx files for SOLUTION_ERP",
"private": true,
"scripts": {
"gen:01": "node _gen-user-01.js",
"gen:02": "node _gen-user-02.js",
"gen:03": "node _gen-user-03.js",
"gen:04": "node _gen-user-04.js",
"gen:05": "node _gen-user-05.js",
"gen:06": "node _gen-user-06.js",
"gen:admin-02": "node _gen-admin-02.js",
"gen:all": "npm run gen:01 && npm run gen:02 && npm run gen:03 && npm run gen:04 && npm run gen:05 && npm run gen:06 && npm run gen:admin-02"
},
"dependencies": {
"docx": "9.5.0"
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.