diff --git a/docs/_user-guide/_gen-admin-02.js b/docs/_user-guide/_gen-admin-02.js new file mode 100644 index 0000000..82bf48d --- /dev/null +++ b/docs/_user-guide/_gen-admin-02.js @@ -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'); +}); diff --git a/docs/_user-guide/_gen-user-01.js b/docs/_user-guide/_gen-user-01.js new file mode 100644 index 0000000..af7b227 --- /dev/null +++ b/docs/_user-guide/_gen-user-01.js @@ -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'); +}); diff --git a/docs/_user-guide/_gen-user-02.js b/docs/_user-guide/_gen-user-02.js new file mode 100644 index 0000000..9cc20a5 --- /dev/null +++ b/docs/_user-guide/_gen-user-02.js @@ -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'); +}); diff --git a/docs/_user-guide/_gen-user-03.js b/docs/_user-guide/_gen-user-03.js new file mode 100644 index 0000000..e85accc --- /dev/null +++ b/docs/_user-guide/_gen-user-03.js @@ -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'); +}); diff --git a/docs/_user-guide/_gen-user-04.js b/docs/_user-guide/_gen-user-04.js new file mode 100644 index 0000000..344fbee --- /dev/null +++ b/docs/_user-guide/_gen-user-04.js @@ -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'); +}); diff --git a/docs/_user-guide/_gen-user-05.js b/docs/_user-guide/_gen-user-05.js new file mode 100644 index 0000000..2aac42e --- /dev/null +++ b/docs/_user-guide/_gen-user-05.js @@ -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'); +}); diff --git a/docs/_user-guide/_gen-user-06.js b/docs/_user-guide/_gen-user-06.js new file mode 100644 index 0000000..c1869e1 --- /dev/null +++ b/docs/_user-guide/_gen-user-06.js @@ -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'); +}); diff --git a/docs/_user-guide/_helpers.js b/docs/_user-guide/_helpers.js new file mode 100644 index 0000000..6fcf0e2 --- /dev/null +++ b/docs/_user-guide/_helpers.js @@ -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, +}; diff --git a/docs/_user-guide/admin/02-Quan-ly-Users-Roles.docx b/docs/_user-guide/admin/02-Quan-ly-Users-Roles.docx new file mode 100644 index 0000000..f87c605 Binary files /dev/null and b/docs/_user-guide/admin/02-Quan-ly-Users-Roles.docx differ diff --git a/docs/_user-guide/package-lock.json b/docs/_user-guide/package-lock.json new file mode 100644 index 0000000..c57878b --- /dev/null +++ b/docs/_user-guide/package-lock.json @@ -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" + } + } + } +} diff --git a/docs/_user-guide/package.json b/docs/_user-guide/package.json new file mode 100644 index 0000000..7eb5f34 --- /dev/null +++ b/docs/_user-guide/package.json @@ -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" + } +} diff --git a/docs/_user-guide/user/01-Bat-dau.docx b/docs/_user-guide/user/01-Bat-dau.docx new file mode 100644 index 0000000..23d753a Binary files /dev/null and b/docs/_user-guide/user/01-Bat-dau.docx differ diff --git a/docs/_user-guide/user/02-Hop-dong.docx b/docs/_user-guide/user/02-Hop-dong.docx new file mode 100644 index 0000000..accecda Binary files /dev/null and b/docs/_user-guide/user/02-Hop-dong.docx differ diff --git a/docs/_user-guide/user/03-Duyet-Workflow.docx b/docs/_user-guide/user/03-Duyet-Workflow.docx new file mode 100644 index 0000000..9a5db98 Binary files /dev/null and b/docs/_user-guide/user/03-Duyet-Workflow.docx differ diff --git a/docs/_user-guide/user/04-PE-Phieu-Duyet-NCC.docx b/docs/_user-guide/user/04-PE-Phieu-Duyet-NCC.docx new file mode 100644 index 0000000..79410f1 Binary files /dev/null and b/docs/_user-guide/user/04-PE-Phieu-Duyet-NCC.docx differ diff --git a/docs/_user-guide/user/05-Budget-Ngan-sach.docx b/docs/_user-guide/user/05-Budget-Ngan-sach.docx new file mode 100644 index 0000000..60037cc Binary files /dev/null and b/docs/_user-guide/user/05-Budget-Ngan-sach.docx differ diff --git a/docs/_user-guide/user/06-7-Loai-HD-Cheatsheet.docx b/docs/_user-guide/user/06-7-Loai-HD-Cheatsheet.docx new file mode 100644 index 0000000..1e3b035 Binary files /dev/null and b/docs/_user-guide/user/06-7-Loai-HD-Cheatsheet.docx differ