[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:
100
docs/_user-guide/_gen-admin-02.js
Normal file
100
docs/_user-guide/_gen-admin-02.js
Normal 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');
|
||||||
|
});
|
||||||
83
docs/_user-guide/_gen-user-01.js
Normal file
83
docs/_user-guide/_gen-user-01.js
Normal 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');
|
||||||
|
});
|
||||||
96
docs/_user-guide/_gen-user-02.js
Normal file
96
docs/_user-guide/_gen-user-02.js
Normal 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');
|
||||||
|
});
|
||||||
94
docs/_user-guide/_gen-user-03.js
Normal file
94
docs/_user-guide/_gen-user-03.js
Normal 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');
|
||||||
|
});
|
||||||
89
docs/_user-guide/_gen-user-04.js
Normal file
89
docs/_user-guide/_gen-user-04.js
Normal 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');
|
||||||
|
});
|
||||||
83
docs/_user-guide/_gen-user-05.js
Normal file
83
docs/_user-guide/_gen-user-05.js
Normal 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');
|
||||||
|
});
|
||||||
168
docs/_user-guide/_gen-user-06.js
Normal file
168
docs/_user-guide/_gen-user-06.js
Normal 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');
|
||||||
|
});
|
||||||
292
docs/_user-guide/_helpers.js
Normal file
292
docs/_user-guide/_helpers.js
Normal 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,
|
||||||
|
};
|
||||||
BIN
docs/_user-guide/admin/02-Quan-ly-Users-Roles.docx
Normal file
BIN
docs/_user-guide/admin/02-Quan-ly-Users-Roles.docx
Normal file
Binary file not shown.
207
docs/_user-guide/package-lock.json
generated
Normal file
207
docs/_user-guide/package-lock.json
generated
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
docs/_user-guide/package.json
Normal file
19
docs/_user-guide/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
docs/_user-guide/user/01-Bat-dau.docx
Normal file
BIN
docs/_user-guide/user/01-Bat-dau.docx
Normal file
Binary file not shown.
BIN
docs/_user-guide/user/02-Hop-dong.docx
Normal file
BIN
docs/_user-guide/user/02-Hop-dong.docx
Normal file
Binary file not shown.
BIN
docs/_user-guide/user/03-Duyet-Workflow.docx
Normal file
BIN
docs/_user-guide/user/03-Duyet-Workflow.docx
Normal file
Binary file not shown.
BIN
docs/_user-guide/user/04-PE-Phieu-Duyet-NCC.docx
Normal file
BIN
docs/_user-guide/user/04-PE-Phieu-Duyet-NCC.docx
Normal file
Binary file not shown.
BIN
docs/_user-guide/user/05-Budget-Ngan-sach.docx
Normal file
BIN
docs/_user-guide/user/05-Budget-Ngan-sach.docx
Normal file
Binary file not shown.
BIN
docs/_user-guide/user/06-7-Loai-HD-Cheatsheet.docx
Normal file
BIN
docs/_user-guide/user/06-7-Loai-HD-Cheatsheet.docx
Normal file
Binary file not shown.
Reference in New Issue
Block a user