Files
X-Financial/server/scripts/build_company_travel_default_workbook.py

260 lines
14 KiB
Python
Raw Normal View History

2026-05-18 02:53:06 +00:00
from __future__ import annotations
from pathlib import Path
from openpyxl import Workbook
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.utils import get_column_letter
OUTPUT_PATH = Path(__file__).resolve().parents[1] / "rules" / "finance-rules" / "公司差旅费报销规则.xlsx"
ROWS = [
(1, "北京", "北京", "", "", 500, 450, 450, 500, ""),
(2, "天津", "6 个中心城区、滨海新区、东丽区、西青区、津南区、北辰区、武清区、宝坻区、静海区、蓟县", "", "", 380, 360, 350, 380, ""),
(2, "天津", "宁河区", "", "", 320, 300, 280, 320, ""),
(3, "河北", "石家庄", "", "", 350, 330, 300, 350, ""),
(3, "河北", "张家口、秦皇岛、廊坊、承德、保定", "张家口市;秦皇岛市;承德市", "张家口市7-9 月、11-3 月秦皇岛市7-8 月承德市7-9 月", 350, 300, 250, 350, 420),
(3, "河北", "雄安新区(不含雄县、安新县、容城县)", "", "", 450, 400, 350, 450, ""),
(3, "河北", "其他地区", "", "", 310, 290, 250, 310, ""),
(4, "山西", "太原", "", "", 350, 330, 300, 350, ""),
(4, "山西", "大同、晋城", "", "", 350, 300, 250, 350, ""),
(4, "山西", "临汾", "", "", 330, 300, 250, 330, ""),
(4, "山西", "阳泉、长治、晋中", "", "", 310, 290, 250, 310, ""),
(4, "山西", "其他地区", "", "", 240, 220, 200, 240, ""),
(5, "内蒙古", "呼和浩特", "", "", 350, 330, 300, 350, ""),
(5, "内蒙古", "海拉尔市、满洲里市、阿尔山市", "海拉尔市、满洲里市、阿尔山市", "7-9 月", 320, 300, 250, 320, 380),
(5, "内蒙古", "二连浩特市", "二连浩特市", "7-9 月", 320, 300, 250, 320, 380),
(5, "内蒙古", "额济纳市", "额济纳市", "9-10 月", 320, 300, 250, 320, 380),
(5, "内蒙古", "其他地区", "", "", 320, 300, 250, 320, ""),
(6, "辽宁", "沈阳", "", "", 350, 330, 300, 350, ""),
(6, "辽宁", "其他地区", "", "", 330, 300, 250, 330, ""),
(7, "大连", "大连", "大连", "7-9 月", 350, 300, 300, 350, 420),
(8, "吉林", "长春", "长春", "7-9 月", 350, 330, 300, 350, 420),
(8, "吉林", "吉林、延边州、长白山管理区", "吉林、延边州、长白山管理区", "7-9 月", 350, 300, 250, 350, 420),
(8, "吉林", "其他地区", "", "", 300, 280, 250, 300, ""),
(9, "黑龙江", "哈尔滨", "哈尔滨", "7-9 月", 350, 330, 300, 350, 420),
(9, "黑龙江", "牡丹江、伊春、大兴安岭地区、黑河、佳木斯", "牡丹江、伊春、大兴安岭地区、黑河、佳木斯", "6-8 月", 300, 280, 250, 300, 360),
(9, "黑龙江", "其他地区", "", "", 300, 280, 250, 300, ""),
(10, "上海", "上海", "", "", 500, 450, 450, 500, ""),
(11, "江苏", "南京", "", "", 380, 350, 350, 380, ""),
(11, "江苏", "苏州、无锡、常州、镇江", "", "", 350, 300, 250, 380, ""),
(11, "江苏", "其他地区", "", "", 350, 300, 250, 360, ""),
(12, "浙江", "杭州", "", "", 400, 350, 350, 400, ""),
(12, "浙江", "其他地区", "", "", 340, 300, 250, 340, ""),
(13, "宁波", "宁波", "", "", 350, 300, 250, 350, ""),
(14, "安徽", "合肥", "", "", 350, 330, 300, 350, ""),
(14, "安徽", "其他地区", "", "", 350, 300, 250, 350, ""),
(15, "福建", "福州", "", "", 380, 350, 300, 380, ""),
(15, "福建", "泉州、平潭综合实验区", "", "", 350, 300, 250, 380, ""),
(15, "福建", "其他地区", "", "", 350, 300, 250, 350, ""),
(16, "厦门", "厦门", "", "", 400, 380, 350, 400, ""),
(17, "江西", "南昌", "", "", 350, 330, 300, 350, ""),
(17, "江西", "其他地区", "", "", 350, 300, 250, 350, ""),
(18, "山东", "济南", "", "", 380, 350, 300, 380, ""),
(18, "山东", "烟台、威海、日照", "烟台、威海、日照", "7-9 月", 350, 300, 250, 380, 450),
(18, "山东", "淄博、枣庄、东营、潍坊、济宁、泰安", "", "", 350, 300, 250, 380, ""),
(18, "山东", "其他地区", "", "", 350, 300, 250, 360, ""),
(19, "青岛", "青岛", "青岛", "7-9 月", 350, 300, 250, 380, 450),
(20, "河南", "郑州", "", "", 380, 350, 300, 380, ""),
(20, "河南", "洛阳", "洛阳", "4-5 月上旬", 330, 300, 250, 330, 390),
(20, "河南", "其他地区", "", "", 330, 300, 250, 330, ""),
(21, "湖北", "武汉", "", "", 350, 330, 300, 350, ""),
(21, "湖北", "其他地区", "", "", 320, 300, 250, 320, ""),
(22, "湖南", "长沙", "", "", 350, 330, 300, 350, ""),
(22, "湖南", "其他地区", "", "", 330, 300, 250, 330, ""),
(23, "广东", "广州", "", "", 450, 400, 400, 450, ""),
(23, "广东", "珠海", "", "", 450, 400, 350, 450, ""),
(23, "广东", "佛山、东莞、中山、江门", "", "", 350, 300, 250, 450, ""),
(23, "广东", "其他地区", "", "", 350, 300, 250, 420, ""),
(24, "深圳", "深圳", "", "", 450, 400, 400, 450, ""),
(25, "广西", "南宁", "", "", 350, 330, 300, 350, ""),
(25, "广西", "桂林、北海", "桂林、北海", "1-2 月、7-9 月", 330, 300, 250, 330, 390),
(25, "广西", "其他地区", "", "", 330, 300, 250, 330, ""),
(26, "海南", "海口、文昌、澄迈县", "海口、文昌、澄迈县", "11-2 月", 350, 330, 310, 350, 420),
(26, "海南", "琼海、万宁、陵水县、保亭县", "琼海、万宁、陵水县、保亭县", "11-3 月", 350, 330, 310, 350, 420),
(26, "海南", "三沙、儋州、五指山、东方、安定县、屯昌县、临高县、白沙县、昌江县、乐东县、琼中县、洋浦开发区", "", "", 350, 330, 310, 350, ""),
(26, "海南", "三亚", "三亚", "10-4 月", 400, 380, 350, 400, 480),
(27, "重庆", "9 个中心城区、北部新区", "", "", 370, 350, 330, 370, ""),
(27, "重庆", "其他地区", "", "", 300, 280, 260, 300, ""),
(28, "四川", "成都", "", "", 370, 350, 330, 370, ""),
(28, "四川", "阿坝州、甘孜州", "", "", 330, 300, 250, 330, ""),
(28, "四川", "绵阳、乐山、雅安", "", "", 320, 300, 250, 320, ""),
(28, "四川", "宜宾", "", "", 300, 280, 250, 300, ""),
(28, "四川", "凉山州", "", "", 330, 300, 250, 330, ""),
(28, "四川", "德阳、遂宁、巴中", "", "", 310, 290, 250, 310, ""),
(28, "四川", "其他地区", "", "", 300, 280, 250, 300, ""),
(29, "贵州", "贵阳", "", "", 370, 350, 300, 370, ""),
(29, "贵州", "其他地区", "", "", 300, 280, 250, 300, ""),
(30, "云南", "昆明", "", "", 380, 350, 300, 380, ""),
(30, "云南", "大理州、丽江市、迪庆州、西双版纳州", "", "", 350, 300, 250, 380, ""),
(30, "云南", "其他地区", "", "", 330, 300, 250, 330, ""),
(31, "西藏", "拉萨", "拉萨", "5-10 月", 350, 330, 300, 350, 420),
(31, "西藏", "其他地区", "其他地区", "5-10 月", 300, 280, 250, 300, 360),
(32, "陕西", "西安", "", "", 350, 330, 300, 350, ""),
(32, "陕西", "榆林、延安", "", "", 300, 280, 250, 300, ""),
(32, "陕西", "杨凌区", "", "", 260, 240, 220, 260, ""),
(32, "陕西", "咸阳、宝鸡", "", "", 260, 240, 220, 260, ""),
(32, "陕西", "渭南、韩城", "", "", 260, 240, 220, 260, ""),
(32, "陕西", "其他地区", "", "", 230, 210, 200, 230, ""),
(33, "甘肃", "兰州", "", "", 350, 330, 300, 350, ""),
(33, "甘肃", "其他地区", "", "", 310, 290, 250, 310, ""),
(34, "青海", "西宁", "西宁", "6-9 月", 350, 330, 300, 350, 420),
(34, "青海", "玉树州", "玉树州", "5-9 月", 300, 280, 250, 300, 360),
(34, "青海", "果洛州", "", "", 300, 280, 250, 300, ""),
(34, "青海", "海北州、黄南州", "海北州、黄南州", "5-9 月", 250, 230, 210, 250, 300),
(34, "青海", "海东、海南州", "海东、海南州", "5-9 月", 250, 230, 210, 250, 300),
(34, "青海", "海西州", "海西州", "5-9 月", 200, 200, 200, 200, 240),
(35, "宁夏", "银川", "", "", 350, 330, 300, 350, ""),
(35, "宁夏", "其他地区", "", "", 330, 300, 250, 330, ""),
(36, "新疆", "乌鲁木齐", "", "", 350, 330, 300, 350, ""),
(36, "新疆", "石河子、克拉玛依、昌吉州、伊犁州、阿勒泰地区、博州、吐鲁番、哈密地区、巴州、和田地区", "", "", 340, 300, 250, 340, ""),
(36, "新疆", "克州", "", "", 320, 300, 250, 320, ""),
(36, "新疆", "喀什地区", "", "", 300, 280, 250, 300, ""),
(36, "新疆", "阿克苏地区", "", "", 300, 280, 250, 300, ""),
(36, "新疆", "塔城地区", "", "", 300, 280, 250, 300, ""),
(37, "港澳台", "香港、澳门、台湾", "", "", 450, 400, 350, 500, ""),
(38, "国外", "国外", "", "", 700, 600, 500, 700, ""),
]
def build_workbook() -> Workbook:
workbook = Workbook()
worksheet = workbook.active
worksheet.title = "差旅住宿费标准"
headers = [
"序号",
"地区",
"地区(城市)",
"旺季地区",
"旺季期间",
"公司级管理人员、高层经理P7及以上",
"中层经理、基层经理P4-P6、外聘专家",
"其他员工",
"超标限额",
"旺季超标限额",
]
worksheet.append(["差旅住宿费标准"])
worksheet.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(headers))
worksheet["A1"].font = Font(bold=True, size=16, color="FFFFFF")
worksheet["A1"].fill = PatternFill("solid", fgColor="1F4E78")
worksheet["A1"].alignment = Alignment(horizontal="center")
worksheet.append(headers)
for row in ROWS:
worksheet.append(row)
header_fill = PatternFill("solid", fgColor="D9EAF7")
thin = Side(style="thin", color="B7C9D6")
for cell in worksheet[2]:
cell.font = Font(bold=True)
cell.fill = header_fill
cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
cell.border = Border(left=thin, right=thin, top=thin, bottom=thin)
for row in worksheet.iter_rows(min_row=3, max_row=worksheet.max_row):
for cell in row:
cell.alignment = Alignment(vertical="center", wrap_text=True)
cell.border = Border(left=thin, right=thin, top=thin, bottom=thin)
for cell in row[5:]:
cell.alignment = Alignment(horizontal="center", vertical="center")
worksheet.freeze_panes = "A3"
worksheet.auto_filter.ref = f"A2:J{worksheet.max_row}"
widths = [8, 12, 42, 28, 28, 22, 26, 12, 12, 14]
for index, width in enumerate(widths, start=1):
worksheet.column_dimensions[get_column_letter(index)].width = width
worksheet.row_dimensions[1].height = 26
worksheet.row_dimensions[2].height = 42
for index in range(3, worksheet.max_row + 1):
worksheet.row_dimensions[index].height = 36
subsidy_sheet = workbook.create_sheet("出差补助标准")
subsidy_headers = [
"补助类型",
"项目",
"港澳台",
"直辖市/特区",
"西藏",
"新疆-乌鲁木齐",
"新疆-其他",
"其他地区",
"国外",
]
subsidy_rows = [
("伙食补助", "自行解决餐食", 75, 65, 65, 55, 55, 55, 140),
("基本补助", "基本出差补贴", 35, 35, 105, 75, 135, 35, 35),
("合计", "", 110, 100, 170, 130, 190, 90, 175),
]
subsidy_sheet.append(["出差补助标准"])
subsidy_sheet.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(subsidy_headers))
subsidy_sheet["A1"].font = Font(bold=True, size=16, color="FFFFFF")
subsidy_sheet["A1"].fill = PatternFill("solid", fgColor="1F4E78")
subsidy_sheet["A1"].alignment = Alignment(horizontal="center")
subsidy_sheet.append(subsidy_headers)
for row in subsidy_rows:
subsidy_sheet.append(row)
subsidy_sheet.append(["备注", "注 1新疆分公司同事出差至乌鲁木齐外的其他新疆地区基本补助标准为 95 元。"])
subsidy_sheet.append(["备注", "注 2西藏分公司同事出差至拉萨市外的其他西藏地区基本补助标准为 35 元。"])
subsidy_sheet.merge_cells(start_row=6, start_column=2, end_row=6, end_column=len(subsidy_headers))
subsidy_sheet.merge_cells(start_row=7, start_column=2, end_row=7, end_column=len(subsidy_headers))
for cell in subsidy_sheet[2]:
cell.font = Font(bold=True)
cell.fill = header_fill
cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
cell.border = Border(left=thin, right=thin, top=thin, bottom=thin)
for row in subsidy_sheet.iter_rows(min_row=3, max_row=7):
for cell in row:
cell.alignment = Alignment(vertical="center", wrap_text=True)
cell.border = Border(left=thin, right=thin, top=thin, bottom=thin)
for cell in subsidy_sheet[5]:
cell.font = Font(bold=True)
cell.fill = PatternFill("solid", fgColor="E2F0D9")
subsidy_sheet.freeze_panes = "A3"
subsidy_sheet.auto_filter.ref = "A2:I5"
subsidy_widths = [14, 18, 12, 16, 12, 18, 16, 14, 12]
for index, width in enumerate(subsidy_widths, start=1):
subsidy_sheet.column_dimensions[get_column_letter(index)].width = width
subsidy_sheet.row_dimensions[1].height = 26
subsidy_sheet.row_dimensions[2].height = 36
for index in range(3, 8):
subsidy_sheet.row_dimensions[index].height = 28
source_sheet = workbook.create_sheet("来源说明")
source_sheet.append(["来源文件", "页码", "说明"])
source_sheet.append(
[
"远光软件2026费用报销说明手册.pdf",
"第 13-19 页",
"依据 PDF 附件 3《差旅住宿费标准》整理为默认支撑表。",
]
)
source_sheet.append(
[
"远光软件2026费用报销说明手册.pdf",
"第 20 页",
"依据 PDF 附件 4《出差补助标准》整理为默认支撑表。",
]
)
for row in source_sheet.iter_rows():
for cell in row:
cell.alignment = Alignment(wrap_text=True, vertical="center")
cell.border = Border(left=thin, right=thin, top=thin, bottom=thin)
for cell in source_sheet[1]:
cell.font = Font(bold=True)
cell.fill = header_fill
source_sheet.column_dimensions["A"].width = 34
source_sheet.column_dimensions["B"].width = 14
source_sheet.column_dimensions["C"].width = 56
return workbook
def main() -> None:
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
workbook = build_workbook()
workbook.save(OUTPUT_PATH)
print(OUTPUT_PATH)
print(f"rows={len(ROWS)}")
if __name__ == "__main__":
main()