見出し画像

今回のシステム運用で発生した問題と対策

エクセルを入力テンプレートとすることの問題

読み込むシートを別に作成し対応する

売上伝票に関しては、フロントの社員が作成したテンプレートをそのまま利用しました。得意先に合わせた請求フォームであったっため、表示を中心にしたシートの構造になっており、異なるフォームがたまに発生しエラーが発生しました。
workbook = load_workbook(file, read_only=True, data_only=True)
との読み込み方で、計算式の値も読み込むことが可能と分かったので、新たに【売上伝票】のシートを作り、【請求書】のシートから必要なデータを「=請求書!B21」の形式で転記する方式を採用しました。この結果、エラーは発生しなくなりました。

入力したエクセルのチェックプログラムを作る

エクセルでは日付の項目のデータが「文字」であったり「通貨」であったりし、これが原因でエラーが発生しました。仕分けを行い会計ソフト(JDL)に取り込む時にエラーが発生したため、集計する前段階でデータの正規化を行うべきだと考えエラーチェックのプログラムを作りました。エラーがある場合には注意を引くように音がでる(beep)を付けました。同時に日付の範囲(2023-10-1 以降)も付けました。
テンプレートを配布して各自がデータを入力するため、社員によりコピー・ペーストを多用する場合があります。表示は「空白」ですが、" "などが入っている場合が存在しました。これらをチェックするためシートのデータを二次元リストに読み込み(sheet_data = []) その後にデータをチェックすることにしました。
また明細行が削除されたシートがあったために、明細行が削除されたか否かもチェックしました。指定の文字のセルが変わったか否かを見ました。
このプログラムを集計するプログラムからも読み込み判定するため、エラーが発生した時 sys.exit(1) としプログラムを中止する命令を書いています。
ファイル名に指定の名前('経費精算')が入っているか否かもチェックしています。

# プログラムで使用するライブラリーを宣言します。
import os
from datetime import datetime
from openpyxl import load_workbook
import sounddevice as sd
import numpy as np
import sys
# ビープ音を鳴らす関数
def beep(frequency, duration):
    sd.play(0.5 * np.sin(2 * np.pi * frequency * np.arange(int(44100 * duration)) / 44100), samplerate=44100)
    sd.wait()

def is_valid_date(data, min_date=None):

    try:
        date_obj = datetime.strptime(str(data), "%Y-%m-%d")
        if min_date is not None and date_obj < min_date:
            return "1"  # 日付が範囲外の場合は、特定の理由を返す
        return "0"  # 日付が範囲内であればTrueを返す
    except ValueError:
        return "2"  # Invalid Date"  # 日付として解釈できない場合は、特定の理由を返す


# フォルダのパス 対象ファイル ファイル名に指定の文字が入っているかをチェックする
#folder_path = 'exl_data_genkin'
folder_path = './exl_data_genkin'
file_extension = '.xlsx'
file_in_name = '経費精算'
# 2023-10-1 日より大きいかどうかを判断する
min_date = datetime(2023, 10, 1)


# フォルダ内のすべての指定の拡張子のファイル名を取得

file_list = [filename for filename in os.listdir(folder_path) if filename.endswith(file_extension)]
file_list = [filename for filename in file_list if not (filename.startswith('.') or '~$' in filename)]
# 隠しファイルの除去

#print(file_list)
# フォルダ内の各エクセルファイルに対して処理を行います
for file_name in file_list:
    if not (file_in_name in file_name):  # 対象となるエクセルファイルを選択
        print(f"ファイル名に【 {file_in_name} 】が無いファイルがあります:  {file_name}")
        beep(500, 0.2)
        #sys.exit(1)  # 終了した状態のフラクを1にする。

        continue

    # エクセルファイルを読み込む
    # ファイル名が '.xlsx' で終わる場合かつ'経費精算表'がファイル名に含まれる場合
    else:

        file_path = os.path.join(folder_path, file_name)
        #print(file_path)
        # Excelファイルを読み取りモードで開く

    with open(file_path, 'rb') as file:
        sheet_data = []
        # ワークブックを読み込む
        workbook = load_workbook(file, read_only=True, data_only=True)
        sheet_names = workbook.sheetnames  # シートの名前を取得する
        #print(sheet_names)
        worksheet = workbook['Sheet1']

        # 指定した範囲の行のデータを読み込む
        i = 1
        for row in worksheet.iter_rows(min_row=12, max_row=31, min_col=1, max_col=9):
            row_data = [cell.value for cell in row]  # 行のデータをリストに格納
            sheet_data.append(row_data)  # 行のデータを二次元リストに追加
            # sheet_data (LIST) に現状のデータを格納。データを可視化して対処するため

        for i, row in enumerate(sheet_data):
            if (row[1] is None or str(row[1]) == "\u3000" or str(row[1]) =="" or str(row[1]) ==" "):
                #print('対象外',i,row)
                continue

            else:
                print('○対象', i+1, row)
                data_cell = str(row[1])[:10]
                print(i, '+', data_cell)
                date_chk = is_valid_date(data_cell, min_date=min_date)
                print(i, '+', data_cell)

                if date_chk == '1' or date_chk == '2':
                    print(f"ファイル【 {file_name} 】の: {i}行 の「日付」をチェックしてください")
                    beep(200, 0.2)
                    sys.exit(1)  # 終了した状態のフラクを1にする。


        goukei_s = worksheet.cell(row=32, column=6).value  #
        print(goukei_s)

        if goukei_s != '合計金額':  # 合計と記載してあるセルが変わったら行の削除があったと判断
            print("***********")
            print(file_name, " の「明細の行数」が足りません。" )
            # 例: 1000Hzのビープ音を500ms間鳴らす
            beep(600, 0.5)
            workbook.close()
            #quit()
            sys.exit(1)  # 終了した状態のフラクを1にする。

Bing に聞いたところopenpyxl を使うとwith open を使う必要が無いとの回答が返ってきました。

openpyxlを使用してエクセルファイルを開く場合、with openを使用する必要はありません。openpyxlのload_workbook関数がファイルの開閉を適切に管理します。


この記事が気に入ったらサポートをしてみませんか?