sciagent code + Gitea Actions CI/CD
CI/CD / backend (push) Failing after 2m8s
CI/CD / frontend (push) Failing after 1m40s
CI/CD / deploy (push) Has been skipped

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Thinh Lam
2026-06-30 09:38:30 +07:00
commit 688fac73e9
1167 changed files with 158244 additions and 0 deletions
@@ -0,0 +1,140 @@
#!/usr/bin/env python3
"""
Smoke / E2E test for application-form PDF export (docxtpl + LibreOffice).
Direct (no HTTP): exercises merge + conversion on this machine / container.
cd be0 && python tools/e2e_application_form_pdf_export.py --direct --out /tmp/e2e-mau.pdf
HTTP: needs FastAPI listening (--url is the API origin, no /api prefix in path).
python tools/e2e_application_form_pdf_export.py --http http://127.0.0.1:4402 --out /tmp/e2e-mau.pdf
Pure curl + jq (no Python on host; official JSON must be wrapped):
cd /path/to/repo && jq -n --slurpfile o fe0/public/assets/bieu_mau_sang_kien_template.json \
'{officialBieuMau: $o[0]}' | curl -sfS -X POST http://127.0.0.1:4402/api/v1/docx/preview-application-form-pdf \
-H 'Content-Type: application/json' -o /tmp/e2e-mau.pdf && file /tmp/e2e-mau.pdf
If the API returns HTTP 501, rebuild/run be0 with LibreOffice (see be0/Dockerfile: libreoffice-writer-nogui).
Docker (stack up):
docker compose exec be0 python tools/e2e_application_form_pdf_export.py --direct --out /tmp/e2e-mau.pdf
Refresh bundled sample after template changes:
cp ../fe0/public/assets/bieu_mau_sang_kien_template.json tools/e2e_sample_official_bieu_mau.json
Exit 0 on success; non-zero on failure.
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any, Dict
BE0_ROOT = Path(__file__).resolve().parent.parent
if str(BE0_ROOT) not in sys.path:
sys.path.insert(0, str(BE0_ROOT))
def _repo_root() -> Path:
return BE0_ROOT.parent
def load_sample_official() -> Dict[str, Any]:
"""officialBieuMau-shaped JSON (same as Review « Xem lại »)."""
candidates = [
Path(__file__).resolve().parent / "e2e_sample_official_bieu_mau.json",
_repo_root() / "fe0/public/assets/bieu_mau_sang_kien_template.json",
]
for p in candidates:
if p.is_file():
with open(p, encoding="utf-8") as f:
data = json.load(f)
break
else:
raise FileNotFoundError(
"No sample official JSON found. Expected tools/e2e_sample_official_bieu_mau.json "
"or fe0/public/assets/bieu_mau_sang_kien_template.json next to be0/."
)
# Visible markers in generated PDF/DOCX
if isinstance(data.get("TRANG BÌA"), dict):
data["TRANG BÌA"]["Tên sáng kiến (Tiếng Việt)"] = "E2E PDF export — tên sáng kiến kiểm thử"
data["TRANG BÌA"]["Năm"] = "2026"
return data
def run_direct(out_path: Path | None) -> bytes:
from src.be01.docx_to_pdf import convert_docx_bytes_to_pdf
from src.be01.fill_application_form import fill_application_form_docx
from src.be01.official_to_data_blank import official_to_data_blank
official = load_sample_official()
ctx = official_to_data_blank(official)
docx = fill_application_form_docx(ctx)
pdf = convert_docx_bytes_to_pdf(docx)
if not pdf.startswith(b"%PDF"):
raise RuntimeError("Output is not a PDF (missing %PDF header).")
if out_path:
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_bytes(pdf)
print(f"Wrote {len(pdf)} bytes → {out_path}")
else:
print(f"OK: generated PDF, {len(pdf)} bytes (no --out)")
return pdf
def run_http(base_url: str, out_path: Path | None) -> bytes:
import urllib.error
import urllib.request
official = load_sample_official()
url = base_url.rstrip("/") + "/api/v1/docx/preview-application-form-pdf"
body = json.dumps({"officialBieuMau": official}).encode("utf-8")
req = urllib.request.Request(
url,
data=body,
method="POST",
headers={"Content-Type": "application/json", "Accept": "application/pdf"},
)
try:
with urllib.request.urlopen(req, timeout=180) as resp:
raw = resp.read()
except urllib.error.HTTPError as e:
detail = e.read().decode("utf-8", errors="replace")
raise SystemExit(f"HTTP {e.code}: {detail}") from e
if not raw.startswith(b"%PDF"):
raise SystemExit(f"Expected PDF, got {len(raw)} bytes, head={raw[:64]!r}")
if out_path:
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_bytes(raw)
print(f"Wrote {len(raw)} bytes → {out_path}")
else:
print(f"OK: HTTP PDF, {len(raw)} bytes")
return raw
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__)
g = ap.add_mutually_exclusive_group(required=True)
g.add_argument("--direct", action="store_true", help="Run docxtpl + LibreOffice in-process")
g.add_argument("--http", metavar="BASE_URL", help="POST to FastAPI (e.g. http://127.0.0.1:4402)")
ap.add_argument("--out", type=Path, metavar="FILE.pdf", help="Write PDF to this path")
args = ap.parse_args()
try:
if args.direct:
run_direct(args.out)
else:
run_http(args.http, args.out)
except FileNotFoundError as e:
print(f"FAIL: {e}", file=sys.stderr)
sys.exit(2)
except Exception as e:
print(f"FAIL: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
+181
View File
@@ -0,0 +1,181 @@
{
"TRANG BÌA": {
"Tên sáng kiến (Tiếng Việt)": "",
"Tác giả/nhóm tác giả sáng kiến": "",
"Đơn vị công tác": "",
"Thông tin liên hệ (Điện thoại, Email)": "",
"Năm": ""
},
"MẪU SỐ 01 - BÁO CÁO MÔ TẢ SÁNG KIẾN": {
"1. Mở đầu": "",
"2. Tên sáng kiến (tên quy trình, giải pháp, phương pháp)": "",
"3. Lĩnh vực áp dụng của sáng kiến": "",
"4. Mô tả sáng kiến": {
"4.1 Tình trạng giải pháp đã biết hoặc hiện trạng công tác khi chưa có sáng kiến": "",
"4.2 Nội dung giải pháp đề nghị công nhận là sáng kiến": {
"Mục đích của sáng kiến": "",
"Về nội dung của sáng kiến": {
"Các bước thực hiện giải pháp": "",
"Các điều kiện cần thiết để áp dụng giải pháp": "",
"Lĩnh vực áp dụng": "",
"Kết quả thu được": "",
"Danh sách đơn vị/cá nhân đã tham gia áp dụng thử hoặc lần đầu": [
{
"TT": "",
"Tên tổ chức/cá nhân": "",
"Địa chỉ": "",
"Lĩnh vực áp dụng sáng kiến": ""
}
]
},
"Về tính mới của sáng kiến": "",
"Về tính hiệu quả": {
"Tạo ra lợi ích kinh tế": "",
"Đem lại hiệu quả trong giảng dạy": "",
"Tăng năng suất lao động": "",
"Nâng cao hiệu quả công việc": "",
"Nâng cao chất lượng công việc, dịch vụ": "",
"Giảm chi phí": "",
"Cải thiện môi trường, điều kiện học tập, làm việc, sống": "",
"Bảo vệ sức khỏe": "",
"Đảm bảo an toàn lao động, PCCC": "",
"Nâng cao khả năng, trình độ, nhận thức, trách nhiệm": ""
}
}
},
"6. Những thông tin cần được bảo mật (nếu có)": "",
"Ngày ký": {
"Ngày": "",
"Tháng": "",
"Năm": ""
},
"Lãnh đạo đơn vị (Ký, ghi rõ họ tên)": "",
"Tác giả sáng kiến (Ký, ghi rõ họ tên)": ""
},
"MẪU SỐ 02 - ĐƠN ĐỀ NGHỊ CÔNG NHẬN SÁNG KIẾN": {
"Đơn vị": "",
"Danh sách tác giả": [
{
"STT": "",
"Họ và tên": "",
"Ngày tháng năm sinh": "",
"Nơi công tác": "",
"Chức danh": "",
"Trình độ chuyên môn": "",
"Tỷ lệ (%) đóng góp vào việc tạo ra sáng kiến": ""
}
],
"Tên sáng kiến đề nghị xét công nhận": "",
"Chủ đầu tư tạo ra sáng kiến": "",
"Lĩnh vực áp dụng sáng kiến": "",
"Ngày sáng kiến được áp dụng": "",
"Nội dung của sáng kiến": "",
"Phân loại sáng kiến (đánh dấu ☑)": {
"Giải pháp kỹ thuật, quản lý, tác nghiệp, ứng dụng tiến bộ kỹ thuật áp dụng cho ĐHYD TP.HCM": false,
"Sáng kiến cải tiến kỹ thuật từ các nghiên cứu khoa học có kết quả được đăng tải trên các tạp chí, hội nghị trong nước và quốc tế": false,
"Sáng kiến cải tiến kỹ thuật từ sách, giáo trình, tài liệu tham khảo": false
},
"Những thông tin cần được bảo mật (nếu có)": "",
"Các điều kiện cần thiết để áp dụng sáng kiến": "",
"Đánh giá lợi ích theo ý kiến của tác giả": "",
"Đánh giá lợi ích theo ý kiến của tổ chức, cá nhân đã tham gia áp dụng sáng kiến lần đầu": "",
"Danh sách những người đã tham gia áp dụng thử hoặc áp dụng sáng kiến lần đầu": [
{
"Số TT": "",
"Họ và tên": "",
"Ngày tháng năm sinh": "",
"Nơi công tác": "",
"Chức danh": "",
"Trình độ chuyên môn": "",
"Nội dung công việc hỗ trợ": ""
}
],
"Ngày ký": {
"Ngày": "",
"Tháng": "",
"Năm": ""
},
"Xác nhận của lãnh đạo Đơn vị": "",
"Tác giả sáng kiến (Ký, ghi rõ họ tên)": ""
},
"MẪU SỐ 03 - BẢN XÁC NHẬN TỶ LỆ (%) ĐÓNG GÓP VÀO VIỆC TẠO RA SÁNG KIẾN": {
"Ngày ký": {
"Ngày": "",
"Tháng": "",
"Năm": ""
},
"1. Tên sáng kiến": "",
"2. Tác giả chính/Đại diện nhóm tác giả sáng kiến": "",
"Chức vụ, đơn vị công tác": "",
"Tỷ lệ đóng góp": [
{
"STT": "",
"Họ và tên": "",
"Đơn vị công tác": "",
"% đóng góp": "",
"Chữ ký xác nhận": ""
}
],
"Tổng % đóng góp": "100",
"Tác giả chính/Đại diện nhóm tác giả sáng kiến (chữ ký và ghi rõ họ tên)": ""
},
"MẪU SỐ 04 - PHIẾU ĐÁNH GIÁ SÁNG KIẾN": {
"1. Tên sáng kiến": "",
"2. Tác giả/đồng tác giả sáng kiến": "",
"Chức vụ, đơn vị công tác": "",
"3. Nội dung đánh giá": {
"Tính mới (Tối đa 40 điểm)": {
"Nhận xét": "",
"Điểm chấm": ""
},
"Tính hiệu quả (Tối đa 60 điểm)": {
"Nhận xét": "",
"Điểm chấm": ""
},
"Tổng cộng": ""
},
"Kết luận": "",
"Ngày ký": {
"Ngày": "",
"Tháng": "",
"Năm": ""
},
"Thành viên Hội đồng (Ký, ghi rõ họ tên)": ""
},
"BẢN CAM KẾT": {
"Ngày ký": {
"Ngày": "",
"Tháng": "",
"Năm": ""
},
"Tiêu đề phụ (Áp dụng đối với cá nhân đăng ký xét công nhận sáng kiến cải tiến kỹ thuật tại Đại học Y Dược TP. Hồ Chí Minh là tác giả của bài báo khoa học)": "",
"I. THÔNG TIN CHỦ THỂ CAM KẾT": {
"Tác giả đăng ký sáng kiến": "",
"CCCD/Hộ chiếu số": "",
"Đơn vị": "",
"Tên Bài báo trong nước/quốc tế là sản phẩm của nhiệm vụ NCKH": "",
"Năm xét công nhận sáng kiến": "",
"Vai trò đối với bài báo (☑ vào ô tương ứng)": {
"Tác giả chính Bài báo trong nước/quốc tế là sản phẩm của nhiệm vụ NCKH": false,
"Đồng tác giả Bài báo trong nước/quốc tế là sản phẩm của nhiệm vụ NCKH": false
}
},
"II. CAM KẾT NỘI DUNG (☑ vào ô tương ứng)": {
"1. Quyền sở hữu đối với bài báo trong nước/quốc tế": {
"Tôi là chủ sở hữu hợp pháp của bài báo hoặc được chủ sở hữu/đồng chủ sở hữu đồng ý cho sử dụng bài báo có tên nêu trên làm sản phẩm đăng ký xét công nhận sáng kiến cải tiến kỹ thuật tại ĐHYD": false,
"Trường hợp bài báo là sản phẩm của nhiệm vụ NCKH: chủ sở hữu bài báo (cơ quan) đồng ý cho tác giả/nhóm tác giả sử dụng bài báo có tên nêu trên để đăng ký xét công nhận sáng kiến cải tiến kỹ thuật tại ĐHYD": false
},
"2. Đồng thuận của đồng tác giả bài báo trong nước/quốc tế": {
"Tất cả đồng tác giả đã biết, đồng ý và ký xác nhận cho phép Tác giả đăng ký sáng kiến được sử dụng bài báo có tên nêu trên để đăng ký xét công nhận sáng kiến cải tiến kỹ thuật tại ĐHYD": false
},
"3. Cam kết bài báo trong nước/quốc tế uy tín": {
"Cá nhân đăng ký xét công nhận sáng kiến cải tiến kỹ thuật tại ĐHYD đối với bài báo trong nước/quốc tế cam kết bài báo không thuộc 'Tạp chí săn mồi'. Tôi xin chịu trách nhiệm kiểm tra, đối chiếu và cung cấp bằng chứng khi được yêu cầu": false
},
"4. Tuân thủ pháp luật sở hữu trí tuệ": {
"Tôi cam kết rằng việc sử dụng bài báo đăng ký xét công nhận sáng kiến tại ĐHYD sẽ không gây tranh chấp về: quyền tác giả/quyền liên quan, quyền sở hữu công nghiệp, tiết lộ bí mật kinh doanh, vi phạm bảo mật dữ liệu của bất kỳ bên thứ ba nào. Tôi chịu trách nhiệm trước pháp luật về tính trung thực, hợp pháp của hồ sơ": false
}
},
"III. HẬU QUẢ PHÁP LÝ KHI THÔNG TIN KHÔNG TRUNG THỰC": "Tôi xin cam kết chịu trách nhiệm đối với các thông tin kê khai nêu trên. Nếu thông tin được khai trong bản cam kết này không đúng thì tôi chấp nhận: Hủy kết quả công nhận sáng kiến đã được xét (nếu có); Thu hồi, hủy các danh hiệu thi đua, khen thưởng, hoặc các quyền lợi phát sinh có sử dụng sáng kiến này để xét; Xử lý theo quy định pháp luật hiện hành và theo quy chế/quy định của ĐHYD. Cam kết này có hiệu lực kể từ ngày ký và ràng buộc đối với cá nhân cam kết trong suốt thời gian xét công nhận sáng kiến và sau khi kết thúc 02 năm.",
"Người cam kết (Ký tên, ghi rõ họ tên)": ""
}
}