Files
X-Financial/server/scripts/build_company_travel_default_workbook.py
2026-05-18 02:53:06 +00:00

260 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()