1
头图

背景

在日常的工作中,我们不免需要面临费用报销问题,在进行费用报销时,我们需要提供费用相关的发票,并需要在报销单上填写相关的金额数据。这时我们将面临核对和计算发票金额的需求。

核对发票

如今,电子发票越来越普及,如果是纸质发票,为了方便管理和后期核对,我们也推荐将纸质发票先扫描或者拍照为电子版,再进行发票的核对的管理。

如下👇,是一个demo, 我们在 totalCommander 软件中,可以很方便的查阅我们选择的发票的内容,并根据图像来修改我们的发票文档名称。
20231014224240

如下👇,我们在widnow资源管理器中也一样可以很方便的选择和预览发票内容。
20231014224610

如下👇,我们在核对发票的时候,我们选择和浏览发票内容,将必要的发票信息体现在发票文档名称中。
20231014224914

按以上操作,将我们需要报销的每张发票都完成信息/金额的核对,我们将得到所有核对后的发票文档及其文档名信息。

计算发票总金额

在 totalCommander 中,我们可以很方便的调用和运行 py 脚本,来对我们已经核对过的发票文档计算其金额总和。效果如下:
20231014225438

脚本开发

以上是核对发票和快速计算发票总金额的一个过程和效果展示,如果你对以上过程和效果有兴趣,我们一起看一下具体的python脚本吧。

python脚本

首先, 欠需要开发用于发票金额解析和计算的python脚本。以下贴出脚本代码:

# -*- coding:UTF-8 -*-

# region 导入必要的依赖包
import os
import re  # 引入 re 模块

模块名 = 'pyperclip'
try:
    import pyperclip  # 需要安装 pyperclip 模块,以支持粘贴板操作
except ImportError as impErr:
    print(f"尝试导入 {模块名} 依赖时检测到异常:{impErr}")
    print(f"尝试安装 {模块名} 模块:")
    try:
        os.system(f"pip install {模块名}")
    except OSError as osErr:
        print(f"尝试安装模块 {模块名} 时检测到异常:{osErr}")
        exit(0)
    else:
        try:
            import pyperclip
        except ImportError as impErr:
            print(f"再次尝试导入 {模块名} 依赖时检测到异常:{impErr}")
            exit(0)

模块名 = 'DebugInfo'
try:
    from DebugInfo.DebugInfo import *
except ImportError as impErr:
    print(f"尝试导入 {模块名} 依赖时检测到异常:{impErr}")
    print(f"尝试安装 {模块名} 模块:")
    try:
        os.system(f"pip install {模块名}")
    except OSError as osErr:
        print(f"尝试安装模块 {模块名} 时检测到异常:{osErr}")
        exit(0)
    else:
        try:
            from DebugInfo.DebugInfo import *
        except ImportError as impErr:
            print(f"再次尝试导入 {模块名} 依赖时检测到异常:{impErr}")
            exit(0)


# endregion


# 定义一个 命令行参数类,用于解析和记录命令行参数
class 命令行参数类(入参基类):
    def __init__(self):
        super().__init__()
        self._添加参数('srcDir', str, '指定需要处理的路径')

    # region 访问器
    @property
    def srcDir(self) -> str:
        if 'srcDir' in self._参数字典:
            return self._参数字典['srcDir'].值
        else:
            return ''

    @srcDir.setter
    def srcDir(self, 值: str):
        if 'srcDir' in self._参数字典:
            if isinstance(值, str):
                self._参数字典['srcDir'].值 = 值
    # endregion


def 提取文档列表(目标路径: str = None, 画板: 打印模板 = None) -> list[str]:
    """
    提取指定路径下的文档,将文档名整理成列表返回
    :param 目标路径: 指定需要处理哪个路径下的文档
    :param 画板: 提供打印输出的模板对象
    :return: list[文档名]
    """
    画板 = 画板 if isinstance(画板, 打印模板) else 打印模板()
    画板.执行位置(提取文档列表)

    文档列表: list[str] = []
    if 目标路径:
        if os.path.isdir(目标路径):
            for itm in os.listdir(目标路径):
                文档列表.append(itm)
        else:
            画板.提示调试错误('tgtDir is not a valid dir: ', 目标路径)
    else:
        画板.提示调试错误('tgtDir is inValid: ', 目标路径)

    return 文档列表


def 金额求和(文档列表: list[str] = None, 画板: 打印模板 = None) -> float:
    """
    解析指定文档名列表中的金额信息(以¥开头的数字),对这些金额进行求和,并自动复制入粘贴板
    :param 文档列表: 指定需要处理的文档名列表
    :param 画板: 提供打印输出的打印模板对象
    :return: 计算的总金额
    """
    画板 = 画板 if isinstance(画板, 打印模板) else 打印模板()
    画板.执行位置(金额求和)

    class 文档信息类:
        def __init__(self):
            self.文档名: str = ''
            self.格式化的文档名: str = ''
            self.金额: float or None = None

        def toStr(self):
            if not self.格式化的文档名:
                self.格式化的文档名 = self.文档名
            if self.金额 is None:
                return 红底(self.格式化的文档名)
            else:
                return str(self.格式化的文档名)

    求和: float = 0
    文档信息表: list[文档信息类] = []
    if 文档列表:
        匹配模式 = r'¥\s*([+-]?\s*\d+\.?\d*)'
        画板.准备表格('c')
        画板.添加分隔行(修饰方法=红字, 适应窗口=True)
        画板.添加一行('序号', '文档', '金额').修饰行(青字)
        序号: int = 0
        for 文档名 in 文档列表:
            文档信息 = 文档信息类()
            文档信息.文档名 = 文档名

            序号 += 1

            匹配集合 = re.findall(匹配模式, 文档名)
            if 匹配集合:
                try:
                    字串: str = 匹配集合[0]
                    文档信息.格式化的文档名 = 文档信息.文档名.replace(字串, 绿字(字串))
                    字串 = re.sub(r'\s', '', 字串)
                    文档信息.金额 = float(字串)
                except Exception as err:
                    画板.提示调试错误('fail to parse ' + 匹配集合[0] + ' to float: ', err)
                    文档信息.金额 = None

            if 文档信息.金额 is not None:
                文档信息表.append(文档信息)
                画板.添加一行(序号, 文档信息.格式化的文档名, 文档信息.金额)
            else:
                画板.添加一行(序号, 文档信息.toStr())

        求和 = 0 if not 文档信息表 else sum([文档信息.金额 for 文档信息 in 文档信息表 if 文档信息.金额 is not None])
        画板.添加分隔行(修饰方法=红字, 适应窗口=True)
        画板.添加一行('', '', 绿字(求和))
        画板.展示表格()

    return 求和


if __name__ == '__main__':
    画板 = 打印模板(True)
    画板.执行位置(__file__)

    入参 = 命令行参数类()
    入参.解析入参(画板=画板.副本.缩进())

    if 入参.srcDir:
        画板.消息('目标路径:', 入参.srcDir)
        文档列表: list[str] = 提取文档列表(目标路径=入参.srcDir, 画板=画板.副本.缩进())
        if 文档列表:
            求和: float = 金额求和(文档列表=文档列表, 画板=画板.副本.缩进())
            求和字串: str = '{:.3f}'.format(求和)
            pyperclip.copy(求和字串)
            画板.消息(f'总金额 ({绿字(求和)}) 已复制')
        else:
            入参.展示()
            画板.提示错误('names\' list is empty: ')
    else:
        画板.提示错误('srcDir 参数无效')
  • 【命令行参数类】
    【命令行参数类】主要用于处理命令行调用时的传入参数。这个类中,我们添加了一个命令行参数:srcDir,表示将要处理的发票文档所在的路径。
  • 【提取文档列表】
    【提取文档列表】方法用于在指定的路径下,读取所有的文档名,并整理成 list[str] 列表对象返回,以供下游处理。
  • 【金额求和】
    【金额求和】方法用于将指定的文档列表,识别文档名中的金额信息,并进行求和计算,然后将所求的总金额值复现到粘贴板中,最后整理并打印显示结果。

bat脚本

如下👇,有了 py 脚本,我们使用时需要以 python xxx.py --srcDir="xxxxxx" 的格式来使用, 但这样的使用方式不够人性化。
20231014230748

为了更方便的使用 py 脚本,我们需要更人性化的使用方式。我们需要借助 totalCommander 来实现快捷调用 py 脚本的能力,我们需要一个为此创建一个bat文档做为媒介。bat脚本如下👇:

::可以通过在total中调用此脚本,达到直接在当前窗口路径下打开cmd窗口的操作

@echo off
set srcDir=%~s1

python %~dp0计算总账.py --srcDir=%srcDir%

echo.
pause

以上脚本中,我们通过接收 totalCommander 调用时传入的路径参数,将其做为 计算总账.py 的 srcDir 参数传入 py 脚本内,从而实现对指定路径下发票文档的金额的计算。

totalCommander 配置

上文提到,我们需要借助 totalComamander 的能力来通过 bat 脚本调用 python 脚本,在totalCommander中, 我们的配置如下👇:
20231014232148

按照上图的步骤,我们可以在 totalCommander 中创建一个快捷键,当我们激活发票文档时,通过该快捷件可以直接调用我们的 py 脚本来进行发票金额的核算。

小结

好了,以上就是这次分享的 py 脚本协助计算发票金额的方法和脚本了。欢迎讨论。


三块钱
7 声望0 粉丝

引用和评论

0 条评论