| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- # backend/tests/test_bx_reimburse_import.py
- from datetime import date
- from io import BytesIO
- from openpyxl import Workbook, load_workbook
- from app.services.bx.reimburse_import import (
- HEADERS,
- build_applicant_label,
- build_expense_path_labels,
- build_import_template_bytes,
- is_example_row,
- iter_data_rows,
- partition_import_groups,
- validate_group_headers,
- )
- from app.utils.excel_list_validation import add_list_validation, write_option_sheet
- def test_build_expense_path_labels_unique_and_enabled_only():
- nodes = [
- {"id": "1", "parent_id": "0", "item_name": "差旅费", "status": 1, "item_code": "CL"},
- {"id": "2", "parent_id": "1", "item_name": "市内交通", "status": 1, "item_code": "JT"},
- {"id": "3", "parent_id": "1", "item_name": "住宿费", "status": 0, "item_code": "ZS"},
- {"id": "4", "parent_id": "0", "item_name": "办公费", "status": 1, "item_code": "BG"},
- ]
- labels = build_expense_path_labels(nodes)
- assert labels["2"] == "差旅费 / 市内交通"
- assert labels["1"] == "差旅费"
- assert labels["4"] == "办公费"
- assert "3" not in labels
- def test_build_expense_path_labels_exclude_orphan_when_parent_missing():
- """父级未传入(等同已删除)时,子节点不应进入下拉——与 Web 树一致。"""
- nodes = [
- {"id": "29", "parent_id": "28", "item_name": "办公用品", "status": 1, "item_code": "BGYP"},
- {"id": "30", "parent_id": "0", "item_name": "物流运输费用", "status": 1, "item_code": "YSF"},
- ]
- labels = build_expense_path_labels(nodes)
- assert "29" not in labels
- assert labels["30"] == "物流运输费用"
- def test_build_expense_path_labels_disambiguate_collision():
- nodes = [
- {"id": "a", "parent_id": "0", "item_name": "同名", "status": 1, "item_code": "A1"},
- {"id": "b", "parent_id": "0", "item_name": "同名", "status": 1, "item_code": "B1"},
- ]
- labels = build_expense_path_labels(nodes)
- assert labels["a"] != labels["b"]
- assert "编码" in labels["a"] or "编码" in labels["b"] or labels["a"].endswith(")")
- def test_cell_str_normalizes_excel_float_ids():
- from app.services.bx.reimburse_import import cell_str
- assert cell_str(29.0) == "29"
- assert cell_str(29) == "29"
- def test_build_applicant_label():
- assert build_applicant_label("张三", "z001", "uid-abcdef") == "张三(z001)"
- assert build_applicant_label("李四", "", "abcdefghij") == "李四(efghij)"
- def test_is_example_row():
- assert is_example_row({"报销事由": "【示例】地铁"}) is True
- assert is_example_row({"报销事由": "正常事由"}) is False
- def test_partition_empty_group_is_solo():
- rows = [
- {"_row": 2, "分组号": "G1", "申请人": "A", "报销日期": "2026-08-18", "报销标题": "", "费用项目": "x", "金额(元)": 1, "报销事由": "r1", "发生时间": ""},
- {"_row": 3, "分组号": "G1", "申请人": "A", "报销日期": "2026-08-18", "报销标题": "", "费用项目": "y", "金额(元)": 2, "报销事由": "r2", "发生时间": ""},
- {"_row": 4, "分组号": "", "申请人": "B", "报销日期": "2026-08-18", "报销标题": "", "费用项目": "z", "金额(元)": 3, "报销事由": "r3", "发生时间": ""},
- ]
- groups = partition_import_groups(rows)
- assert len(groups) == 2
- g1 = next(g for g in groups if g["group_no"] == "G1")
- assert len(g1["rows"]) == 2
- solo = next(g for g in groups if g["group_no"] == "")
- assert len(solo["rows"]) == 1
- def test_validate_group_headers_mismatch():
- rows = [
- {"_row": 2, "申请人": "A", "报销日期": "2026-08-18", "报销标题": ""},
- {"_row": 3, "申请人": "B", "报销日期": "2026-08-18", "报销标题": ""},
- ]
- err = validate_group_headers(rows)
- assert err and "申请人" in err
- def test_validate_group_headers_title_partial_empty():
- rows = [
- {"_row": 2, "申请人": "A", "报销日期": "2026-08-18", "报销标题": "有标题"},
- {"_row": 3, "申请人": "A", "报销日期": "2026-08-18", "报销标题": ""},
- ]
- err = validate_group_headers(rows)
- assert err and "标题" in err
- def test_write_option_sheet_and_validation():
- wb = Workbook()
- ws = wb.active
- ws.title = "报销导入"
- ws.append(["申请人"])
- write_option_sheet(wb, "_选项_申请人", ["张三(z001)"], ["u1"], hidden=True)
- add_list_validation(
- ws,
- option_sheet="_选项_申请人",
- option_rows=1,
- target_col=1,
- start_row=2,
- end_row=100,
- )
- assert wb["_选项_申请人"].sheet_state == "hidden"
- assert len(ws.data_validations.dataValidation) == 1
- def test_build_import_template_has_guide_examples_and_dv():
- raw = build_import_template_bytes(
- expense_nodes=[
- {"id": "1", "parent_id": "0", "item_name": "差旅费", "status": 1, "item_code": "CL"},
- {"id": "2", "parent_id": "1", "item_name": "市内交通", "status": 1, "item_code": "JT"},
- ],
- applicants=[{"id": "u1", "nick_name": "张三", "user_no": "z001"}],
- current_applicant_label="张三(z001)",
- today=date(2026, 8, 18),
- )
- wb = load_workbook(BytesIO(raw))
- assert "填写说明" in wb.sheetnames
- assert "报销导入" in wb.sheetnames
- assert wb["_选项_费用项目"].sheet_state == "hidden"
- ws = wb["报销导入"]
- assert [c.value for c in ws[1]][:8] == list(HEADERS)
- assert str(ws["G2"].value).startswith("【示例】")
- assert len(ws.data_validations.dataValidation) >= 1
- def test_iter_data_rows_skips_examples():
- wb = Workbook()
- ws = wb.active
- ws.title = "报销导入"
- ws.append(list(HEADERS))
- ws.append(
- ["G1", "张三(z001)", "2026-08-18", "", "差旅费", 50, "【示例】跳过", ""]
- )
- ws.append(
- ["G1", "张三(z001)", "2026-08-18", "", "差旅费", 80, "正式事由", ""]
- )
- rows = iter_data_rows(ws)
- assert len(rows) == 1
- assert rows[0]["报销事由"] == "正式事由"
- assert rows[0]["_row"] == 3
- def test_build_annotated_result_bytes_appends_status_columns():
- from app.services.bx.reimburse_import import build_annotated_result_bytes
- wb = Workbook()
- ws = wb.active
- ws.title = "报销导入"
- ws.append(list(HEADERS))
- ws.append(["G1", "张三(z001)", "2026-08-18", "", "差旅费", 80, "正式事由", ""])
- buf = BytesIO()
- wb.save(buf)
- raw = build_annotated_result_bytes(
- buf.getvalue(),
- {2: {"status": "成功", "message": "成功,单号 BX001"}},
- )
- out = load_workbook(BytesIO(raw))["报销导入"]
- headers = [c.value for c in out[1]]
- assert "导入结果" in headers
- assert "失败原因" in headers
- status_i = headers.index("导入结果") + 1
- assert out.cell(row=2, column=status_i).value == "成功"
|