【Python】openpyxlとTyperで在庫報告書作成CLIツールを作ってみた

【Python】openpyxlとTyperで在庫報告書作成CLIツールを作ってみた

公開:
CodeCampが提供するDX人材育成が可能なプログラミングやITが学べる公開講座

毎月の在庫報告書をExcelで手作業更新するのは、地味に手間がかかります。そこでCSVの在庫データを渡すだけで、在庫一覧・在庫金額・補充対象の3シートを持つExcel帳票を作るCLIツールを、PythonのopenpyxlTyperで作ってみました。

sample-csvでお試しデータを作り、buildで帳票を生成し、inspectで中身を読み返す3コマンド構成です。実際に4回のコマンドで動かした結果まで、初心者の方が追体験できるように順を追って解説します。

openpyxlの基本概念、要件定義、実装、動作確認までを順番に学べる構成です。動画は目次から確認したい場面へ移動でき、本文だけでも手順と考え方が完結します。

【Python】openpyxlとTyperで在庫報告書作成CLIツールを作ってみた|フルカリキュラム
動画の内容をテキストで確認する

オープニング。openpyxlとTyperを使って在庫報告書作成CLIを作るカリキュラムを始めます。概要紹介。

openpyxlとTyperの役割と使い方を学ぶ 在庫報告書作成CLIの要件を整理する 完成コードと実行結果を確認する 最後に実コマンドとファイル状態で完成挙動を確かめる 具体的にやること。

sample-csvコマンドで動作確認用の在庫CSVを出力する --rowsと--seedで明細行数と乱数シードを指定する 商品コードから発注点までの必須6列をそろえて書き出すbuildコマンドで在庫CSVを読み込んで明細を組み立てる 在庫数が発注点以下の行へ要補充の判定を付ける 実装環境・必須アプリ。

OS:Windows 11 Pro Python:3.13.3シェル:PowerShell 5.1必須アプリ:コードエディター、ターミナル、エクスプローラー パッケージ:pip、openpyxl、typer PythonのopenpyxlとTyperとは。

openpyxl:openpyxlは、Excelファイルの作成と読み取りをPythonから直接行えるライブラリTyper:Typerは、Pythonの関数をそのままコマンドとして公開できるCLIライブラリ openpyxlとTyperで作る在庫報告書CLIの要点。

在庫金額は単価と在庫数の積 判定は在庫数が発注点以下なら要補充 不足数は発注点から在庫数を引いた値で下限は0 在庫報告書作成CLIの要件定義。

sample-csvの実行でCSVが作られ明細行数が表示される サンプルCSVの先頭3行がプレビュー表示されるbuildの実行でxlsxが保存されシート3枚の名前が並ぶ 在庫数合計と在庫金額合計が桁区切りで表示される 補充対象件数と発注予定金額が表示されるinspectの実行でシート数3と各シートの行数と列数が出る INTRO: Monaco Editorで在庫報告書作成CLIを実装。

コードを1行ずつ入力し、補完と自動インデントを使いながら実行結果を確認します。LINE 001: モジュール全体の説明文。このツールがopenpyxlとTyperを使って在庫報告書を作成するものであることを示す説明文です。

プログラム全体の目的を先頭でまとめています。LINE 003: docstringの補足説明。CSVの在庫データから在庫一覧・在庫金額・補充対象の3シートを持つExcel帳票を作ることを説明しています。

処理の全体像を利用者に伝える役割です。LINE 004: docstringの終了。モジュール冒頭の説明文を閉じる記号です。

ここまでがファイル全体の目的を説明するコメント部分になります。LINE 006: csvモジュールの読み込み。CSVファイルの読み書きを行うための標準ライブラリを読み込んでいます。

この後の在庫データの読み込みや出力で使用します。LINE 007: randomモジュールの読み込み。乱数を生成するための標準ライブラリを読み込んでいます。

サンプルデータの価格や在庫数を作る際に利用します。LINE 008: Pathクラスの読み込み。ファイルパスを扱いやすくするPathクラスを読み込んでいます。

CSVやExcelファイルのパス操作に使用します。LINE 009: Optional型の読み込み。値が存在しない場合を表す型ヒント用のOptionalを読み込んでいます。

コマンドの引数を省略可能にする際に使います。LINE 011: typerライブラリの読み込み。コマンドラインツールを簡単に作成できるtyperライブラリを読み込んでいます。

この後のコマンド定義で使用します。LINE 012: openpyxlの主要機能の読み込み。Excelファイルを新規作成するWorkbookと、既存ファイルを読み込むload_workbookを読み込んでいます。

帳票の作成と確認の両方に使います。LINE 013: セル装飾用クラスの読み込み。文字の書式や罫線、塗りつぶしなどセルの見た目を整えるためのクラス群を読み込んでいます。

見やすい帳票デザインに使用します。LINE 014: 列番号を列名に変換する関数の読み込み。数字の列番号をA、Bなどの列名に変換する関数を読み込んでいます。

列幅指定やセル範囲の指定に利用します。LINE 017: CSVの列名一覧の定義。読み込むCSVに必要な列名を一覧にした定数です。

この列名を使って必要な列が揃っているかを確認します。LINE 018: 在庫一覧シート名の定義。在庫一覧シートの名前を定数として定義しています。

シート作成時にこの名前を使うことで名称の統一を図ります。LINE 019: 在庫金額シート名の定義。在庫金額シートの名前を定数として定義しています。

ほかの場所でも同じ名前を使い回せるようにしています。LINE 020: 補充対象シート名の定義。補充対象シートの名前を定数として定義しています。

シート作成やシート名の一覧表示で使用します。LINE 022: タイトル文字の書式定義。シートの見出しに使う太字で大きめのフォントを定義しています。

各シートのタイトル行に適用します。LINE 023: ヘッダー文字の書式定義。表の見出し行に使う白色で太字のフォントを定義しています。

ヘッダー行を目立たせるために使用します。LINE 024: ヘッダー背景色の定義。表の見出し行の背景を濃い青色で塗りつぶすための設定です。

ヘッダー行の視認性を高めます。LINE 025: 注意喚起用の背景色の定義。補充が必要な行などを目立たせるための薄い赤色の塗りつぶし設定です。

要注意のデータを強調する際に使います。LINE 026: 合計行用フォントの定義。合計行の文字を太字にするための書式設定です。

合計値を通常のデータと区別しやすくします。LINE 027: 罫線の細い線種の定義。薄いグレー色の細い罫線を定義しています。

表の各セルの境界線として使われます。LINE 028: 通常セルの罫線の定義。上下左右すべてに細い罫線を引く設定です。

データ行の各セルに適用して表を見やすくします。LINE 029: 合計行の罫線の定義。上側だけ二重線にした罫線を定義しています。

合計行の上部を強調して区切りを明確にします。LINE 030: 円形式の表示形式の定義。数値を3桁区切りのカンマ付きで表示する書式です。

金額を表すセルに使用します。LINE 031: 整数の表示形式の定義。数値を3桁区切りのカンマ付きで表示する書式です。

在庫数や品目数などの整数値に使用します。LINE 032: 割合の表示形式の定義。小数を百分率表示に変換する書式です。

構成比の列に適用してパーセント表示にします。LINE 034: Typerアプリの作成。コマンドラインツールの本体となるtyper.Typerのインスタンスを作成しています。

この後のコマンドはすべてこのappに登録されます。RUN 1/8: 共通設定とTyperアプリの定数を確認する。CSVの列名、シート名、セル書式の定数までを入力した状態です。

帳票全体で使い回す設定がそろっているかを表示して確かめます。CHECK 1/8: 途中実行に成功。

CSV列: 商品コード | 商品名 | カテゴリ | 単価 | 在庫数 | 発注点 シート名: 在庫一覧 在庫金額 補充対象 金額書式: #,##0 / 構成比書式: 0.0% 見出し色: 001F4E78 / 警告色: 00FCE4E4 RETURN 01: エディターへ戻りました。入力済みのコードを維持したまま、次の実装へ進みます。

LINE 037: サンプル商品リストの開始。サンプルCSVを作るための商品データ一覧の定義を開始しています。カテゴリと商品名の組み合わせをリストとして持ちます。

LINE 038: 文具カテゴリの商品1。文具カテゴリに属するボールペンの商品データです。サンプルCSV生成時の商品候補として使われます。

LINE 039: 文具カテゴリの商品2。文具カテゴリに属するリングノートの商品データです。サンプルデータの一つとして登録されています。

LINE 040: 文具カテゴリの商品3。文具カテゴリに属する蛍光マーカーの商品データです。サンプル生成時に使われる候補の一つです。

LINE 041: 文具カテゴリの商品4。文具カテゴリに属するクリアファイルの商品データです。文具カテゴリの最後の候補になっています。

LINE 042: 事務用品カテゴリの商品1。事務用品カテゴリに属するコピー用紙の商品データです。次のカテゴリの最初の候補になっています。

LINE 043: 事務用品カテゴリの商品2。事務用品カテゴリに属する封筒の商品データです。サンプルデータの候補として追加されています。

LINE 044: 事務用品カテゴリの商品3。事務用品カテゴリに属するレターケースの商品データです。サンプル生成に利用される候補の一つです。

LINE 045: 事務用品カテゴリの商品4。事務用品カテゴリに属するデスクマットの商品データです。事務用品カテゴリの最後の候補になっています。

LINE 046: PC周辺カテゴリの商品1。PC周辺カテゴリに属するUSBメモリの商品データです。次のカテゴリの最初の候補になっています。

LINE 047: PC周辺カテゴリの商品2。PC周辺カテゴリに属する無線マウスの商品データです。サンプルデータの候補として追加されています。

LINE 048: PC周辺カテゴリの商品3。PC周辺カテゴリに属するHDMIケーブルの商品データです。サンプル生成に利用される候補の一つです。

LINE 049: PC周辺カテゴリの商品4。PC周辺カテゴリに属するノートPCスタンドの商品データです。PC周辺カテゴリの最後の候補になっています。

LINE 050: 備品カテゴリの商品1。備品カテゴリに属する折りたたみ椅子の商品データです。次のカテゴリの最初の候補になっています。

LINE 051: 備品カテゴリの商品2。備品カテゴリに属するスチールラックの商品データです。サンプルデータの候補として追加されています。

LINE 052: 備品カテゴリの商品3。備品カテゴリに属するホワイトボードの商品データです。サンプル生成に利用される候補の一つです。

LINE 053: 備品カテゴリの商品4。備品カテゴリに属する電源タップの商品データです。備品カテゴリの最後の候補になっています。

LINE 054: 消耗品カテゴリの商品1。消耗品カテゴリに属する乾電池の商品データです。次のカテゴリの最初の候補になっています。

LINE 055: 消耗品カテゴリの商品2。消耗品カテゴリに属するアルコール除菌シートの商品データです。サンプルデータの候補として追加されています。

LINE 056: 消耗品カテゴリの商品3。消耗品カテゴリに属するゴミ袋の商品データです。サンプル生成に利用される候補の一つです。

LINE 057: 消耗品カテゴリの商品4。消耗品カテゴリに属するインクカートリッジの商品データです。サンプル商品リストの最後の候補になっています。

LINE 058: サンプル商品リストの終了。サンプル商品データ一覧の定義を閉じる記号です。ここまでの20件の商品がサンプルCSV作成の元データとして使われます。

LINE 061: サンプル行生成関数の定義。指定した行数と乱数シードを受け取り、サンプルの在庫明細データを組み立てる関数の定義です。この関数がサンプルCSV作成の中心処理になります。

LINE 062: 関数の説明文。この関数が乱数シードを固定してサンプル在庫明細を作ることを説明する文です。同じシード値なら同じ結果が得られることを示しています。

LINE 063: 乱数生成器の作成。指定したシード値を使って乱数生成器を作成しています。同じシードを渡せば毎回同じ乱数の並びが得られるようになります。

LINE 064: 結果を格納するリストの準備。生成したサンプルデータを入れておくための空のリストを用意しています。この後のループでデータが追加されていきます。

LINE 065: 指定行数分の繰り返し処理。指定された行数の分だけ処理を繰り返すループです。1回のループで1件分の在庫明細データを作成します。

LINE 066: 商品カテゴリと商品名の取得。サンプル商品リストから順番に商品を取り出し、カテゴリと商品名に分けています。商品数を超えた場合は先頭から繰り返し使われます。

LINE 067: 周回数の計算。商品リストを何周目使っているかを計算しています。同じ商品を2周目以降に使うときの型番表示に利用します。

LINE 068: 表示用商品名の作成。1周目はそのままの商品名を使い、2周目以降は型番を付けた名前に変えています。同じ商品名が重複しないよう工夫しています。

LINE 069: 単価のランダム選択。あらかじめ用意した価格候補の中からランダムに単価を選んでいます。サンプルデータに現実的な価格のばらつきを持たせます。

LINE 070: 発注点のランダム選択。あらかじめ用意した発注点候補の中からランダムに値を選んでいます。補充判断の基準となる値になります。

LINE 071: 在庫数のランダム生成。発注点の3倍までの範囲でランダムな在庫数を生成しています。0になることもあり、補充対象になるデータも作られます。

LINE 072: レコードの追加開始。1件分の在庫明細データを辞書としてrecordsリストに追加する処理を開始しています。この後の行で各項目の値を設定します。

LINE 073: レコード辞書の開始。1件分のデータをまとめる辞書リテラルの開始です。以降の行でキーと値の組を順に定義していきます。

LINE 074: 商品コードの設定。連番を使って「SKU-」から始まる商品コードを作成しています。1001番から始まる一意のコードになります。

LINE 075: 商品名の設定。先ほど作成した表示用の商品名をレコードに設定しています。この値がCSVの商品名列に出力されます。

LINE 076: カテゴリ情報の格納。辞書の「カテゴリ」キーに、先ほど取得したカテゴリ名を設定しています。この値は後で在庫一覧シートの表示や集計に使われます。

LINE 077: 単価情報の格納。辞書の「単価」キーに、乱数で選んだ単価priceを設定しています。ここで決まった金額が在庫金額の計算に使われます。

LINE 078: 在庫数情報の格納。辞書の「在庫数」キーに、乱数で決めた在庫数stockを設定しています。この値は補充判定などの計算元になります。

LINE 079: 発注点情報の格納。辞書の「発注点」キーに、補充の目安となるreorder_pointを設定しています。在庫数と比較して補充が必要かを判断する基準になります。

LINE 080: 1件分の辞書の閉じ括弧。1商品分のデータをまとめる辞書リテラルを閉じています。ここまでの各キーで1商品分の情報がひとつの辞書にまとまります。

LINE 081: レコードの追加終了。records.appendの呼び出しを閉じ、作成した1件分の辞書をrecordsリストへ追加する処理を完了させています。LINE 082: サンプルデータの返却。

生成したすべての商品データが入ったrecordsリストを関数の戻り値として返しています。呼び出し側ではこのリストをCSV出力に使います。RUN 2/8: サンプル明細の生成結果を確かめる。

_build_sample_rowsまで入力した時点です。指定した件数の明細が作られること、同じシードなら同じデータが再現されることを確認します。CHECK 2/8: 途中実行に成功。

生成件数: 6 SKU-1001油性ボールペン0.7mm 128 32 15 SKU-1002リングノートA5 1980 7 5 SKU-1003蛍光マーカー5色セット3480 71 30 同じシードで再現一致: True RETURN 02: エディターへ戻りました。入力済みのコードを維持したまま、次の実装へ進みます。

LINE 086: CSV読み込み関数の定義。CSVファイルのパスを受け取り、在庫データのリストを作成して返す関数_load_itemsを定義しています。この関数がCSV読み込みの中心処理です。

LINE 087: 結果格納用リストの準備。読み込んだ商品データを1件ずつ追加していくための空リストitemsを用意しています。この後の処理でここにデータが積み上がっていきます。

LINE 088: CSVファイルのオープン。指定されたパスのCSVファイルをutf-8-sig形式で開いています。BOM付きのCSVでも文字化けせずに読み込めるようにしています。

LINE 089: 辞書形式での読み込み準備。csv.DictReaderを使い、CSVの各行を列名をキーとした辞書として読み取れるように設定しています。以降の行処理がしやすくなります。

LINE 090: 必須列の不足チェック用リスト作成。必要な列名CSV_FIELDSのうち、実際のCSVのヘッダーに存在しないものをmissingリストとして抽出しています。列の過不足を確認するための下準備です。

LINE 091: 不足列の有無判定。missingリストに要素があるかどうか、つまり不足している列が存在するかを判定しています。LINE 092: 列不足時のエラー通知。

必要な列が足りない場合に、不足している列名を含めたエラーメッセージを表示して処理を中断しています。ユーザーにCSVの修正を促します。LINE 093: 行ごとのループ開始。

CSVの各行をrowとして取り出しながら、行番号line_noを2から数えてループしています。2から始めるのはヘッダー行を1行目とみなすためです。LINE 094: 数値変換の例外処理開始。

単価・在庫数・発注点の文字列を数値に変換する処理をtryブロックで囲み、変換エラーに備えています。LINE 095: 単価の数値変換。CSVの「単価」列の値を文字列として取り出し、前後の空白を除いてから整数priceに変換しています。

LINE 096: 在庫数の数値変換。CSVの「在庫数」列の値を文字列として取り出し、空白を除去してから整数stockに変換しています。LINE 097: 発注点の数値変換。

CSVの「発注点」列の値を文字列として取り出し、空白を除去してから整数pointに変換しています。LINE 098: 変換失敗時の例外捕捉。数値変換に失敗したときに発生するTypeErrorまたはValueErrorを捕まえて、次の行でエラー処理を行う準備をしています。

LINE 099: 数値解釈エラーの通知。数値に変換できなかった場合、その行番号を含めたエラーメッセージを表示して処理を中断しています。どの行に問題があるか分かりやすくしています。

LINE 100: 補充要否の判定。在庫数stockが発注点point以下かどうかを比較し、補充が必要かどうかをneed_orderという真偽値に保存しています。LINE 101: 商品データの追加開始。

itemsリストへ1商品分の辞書を追加するためのappend呼び出しを開始しています。LINE 102: 1商品分の辞書の開始。CSVから読み取った値と計算結果をまとめて格納するための辞書リテラルを開始しています。

LINE 103: 商品コードの格納。CSVの「商品コード」列の値を文字列化し、余分な空白を取り除いてから辞書に格納しています。LINE 104: 商品名の格納。

CSVの「商品名」列の値を文字列化し、空白を除いてから辞書に格納しています。この値は在庫一覧シートなどで表示されます。LINE 105: カテゴリの格納。

CSVの「カテゴリ」列の値を文字列化し、空白を除いてから辞書に格納しています。カテゴリ別集計に使われる値です。LINE 106: 単価の格納。

すでに数値変換済みの単価priceを辞書に格納しています。在庫金額などの計算に使われます。LINE 107: 在庫数の格納。

数値変換済みの在庫数stockを辞書に格納しています。判定や集計処理の基礎データになります。LINE 108: 発注点の格納。

数値変換済みの発注点pointを辞書に格納しています。補充判定の基準値として使われます。LINE 109: 在庫金額の計算と格納。

単価と在庫数を掛け合わせて在庫金額を求め、辞書の「在庫金額」に格納しています。この金額は在庫金額シートの集計に使われます。LINE 110: 不足数の計算と格納。

発注点から在庫数を引いた値と0のうち大きい方を不足数として計算し、辞書に格納しています。マイナスにならないようにしています。LINE 111: 補充推奨数の計算と格納。

補充が必要な場合に、発注点の2倍から在庫数を引いた値を補充推奨数として計算し、そうでなければ0を格納しています。LINE 112: 判定結果の格納。need_orderの値に応じて「要補充」または「適正」という文字列を判定結果として辞書に格納しています。

LINE 113: 1商品分の辞書の閉じ括弧。1商品分のデータをまとめる辞書リテラルを閉じています。ここまでの項目で1商品分のすべての情報が揃います。

LINE 114: itemsへの追加終了。items.appendの呼び出しを閉じ、作成した1商品分の辞書をitemsリストへ追加する処理を完了させています。LINE 115: 明細有無のチェック。

CSVを読み込んだ結果、itemsリストが空かどうかを判定しています。1件も明細がない場合の異常を検知します。LINE 116: 明細なしエラーの通知。

CSVに明細行が1件もなかった場合に、エラーメッセージを表示して処理を中断しています。LINE 117: 読み込んだ在庫データの返却。読み込みと計算が完了したitemsリストを関数の戻り値として返しています。

以降の処理ではこのリストを使って集計や帳票作成を行います。RUN 3/8: CSVを読み込んで明細へ変換する。_load_itemsまで入力した状態です。

サンプル明細をCSVへ書き出してから読み込み、在庫金額や判定が付与されるかを見ます。CHECK 3/8: 途中実行に成功。読み込み件数: 8 要補充: 2件 先頭明細: 油性ボールペン0.7mm 4096適正 RETURN 03: エディターへ戻りました。

入力済みのコードを維持したまま、次の実装へ進みます。LINE 121: カテゴリ集計関数の定義。商品データのリストitemsを受け取り、カテゴリごとの集計結果と合計金額を返す関数_summarize_by_categoryを定義しています。

LINE 122: 集計結果の格納辞書を準備。カテゴリ名をキーとして集計値を保持するための空辞書totalsを用意しています。この後のループで内容が積み上がっていきます。

LINE 123: 商品データのループ開始。itemsリストから商品データを1件ずつ取り出し、カテゴリ別の集計処理を行うループを開始しています。LINE 124: カテゴリ別集計の初期化取得。

totals辞書から該当カテゴリの集計データを取得し、まだ存在しない場合は品目数・在庫数・在庫金額を0で初期化して取得しています。LINE 125: 品目数の加算。該当カテゴリの品目数を1件分増やしています。

同じカテゴリの商品が何件あるかを数える処理です。LINE 126: 在庫数の積算。該当カテゴリの在庫数合計に、現在の商品の在庫数を加算しています。

カテゴリ全体の在庫数を求める処理です。LINE 127: 在庫金額の積算。該当カテゴリの在庫金額合計に、現在の商品の在庫金額を加算しています。

カテゴリ全体の在庫金額を求める処理です。LINE 128: 全カテゴリの合計金額算出。totals辞書に集計された各カテゴリの在庫金額を合計し、全体の在庫金額合計total_amountを求めています。

LINE 129: 集計出力用リストの準備。カテゴリごとの集計結果を格納していくための空リストsummaryを用意しています。この後で最終的な出力形式に整形します。

LINE 130: 在庫金額順のソート処理開始。totals辞書の各カテゴリを在庫金額の大きい順に並べ替えながらループしています。金額の大きいカテゴリから先に表示するための準備です。

LINE 131: 構成比の計算。該当カテゴリの在庫金額を全体の合計金額で割り、構成比shareを求めています。合計金額が0の場合は0.0としてゼロ除算を防いでいます。

LINE 132: 集計結果の追加開始。summaryリストへ1カテゴリ分の集計結果をまとめた辞書を追加するためのappend呼び出しを開始しています。LINE 133: カテゴリ集計辞書の開始。

1カテゴリ分の集計値をまとめる辞書リテラルを開始しています。LINE 134: カテゴリ名の格納。集計対象のカテゴリ名を辞書の「カテゴリ」キーに格納しています。

在庫金額シートでの表示に使われます。LINE 135: 品目数の格納。集計済みの品目数bucketを辞書の「品目数」キーに格納しています。

LINE 136: 在庫数合計の格納。集計済みの在庫数合計を辞書の「在庫数」キーに格納しています。LINE 137: 在庫金額合計の格納。

集計済みの在庫金額合計を辞書の「在庫金額」キーに格納しています。LINE 138: 構成比の格納。先ほど計算した構成比shareを辞書の「構成比」キーに格納しています。

パーセント表示で帳票に反映されます。LINE 139: カテゴリ集計辞書の閉じ括弧。1カテゴリ分の集計値をまとめる辞書リテラルを閉じています。

LINE 140: summaryへの追加終了。summary.appendの呼び出しを閉じ、作成した1カテゴリ分の辞書をsummaryリストへ追加する処理を完了させています。LINE 141: 集計結果と合計金額の返却。

カテゴリ別集計結果summaryと全体の合計金額total_amountをタプルとして関数の戻り値にしています。呼び出し側で在庫金額シート作成に使われます。RUN 4/8: カテゴリ別の集計結果を表示する。

_summarize_by_categoryまで入力した状態です。小さな明細を渡して、在庫金額の降順並びと構成比の計算を確かめます。CHECK 4/8: 途中実行に成功。

カテゴリ数: 2 / 在庫金額合計: 12400 PC周辺1 5 6400 0.516 文具2 42 6000 0.484 RETURN 04: エディターへ戻りました。入力済みのコードを維持したまま、次の実装へ進みます。LINE 145: シート見出し作成関数の定義。

シートws、タイトル、見出し項目、列幅を受け取り、シート冒頭のタイトル行と見出し行を作成する関数_write_sheet_headを定義しています。LINE 146: タイトルセルの設定。シートの1行1列目にタイトル文字列を書き込み、その見た目をTITLE_FONTで太字大きめに設定しています。

LINE 147: 見出し項目のループ開始。headersとwidthsをペアにしながら、列番号colを1から数えて見出しと列幅を設定するループを開始しています。LINE 148: 見出しセルの書き込み。

シートの2行目、col列目に見出し文字列headerを書き込み、そのセルをcell変数に取得しています。この後の行で書式を設定していきます。LINE 149: 見出しフォントの設定。

見出しセルのフォントを太字・白文字のHEADER_FONTに設定しています。これにより見出し行が本文と区別しやすくなります。LINE 150: 見出し背景色の設定。

見出しセルの塗りつぶし色をHEADER_FILLの濃い青系に設定しています。ヘッダー部分が視覚的に目立つようになります。LINE 151: 見出しの中央揃え。

見出しセルの文字を横方向・縦方向ともに中央揃えにしています。見出しの見た目を整える処理です。LINE 152: 見出しに罫線を設定。

見出しセルにCELL_BORDERの薄いグレーの罫線を適用しています。セルの区切りが分かりやすくなります。LINE 153: 列幅の設定。

列番号から列記号を求めて、その列の幅をwidthの値に設定しています。文字数に合わせて見やすい列幅を指定する処理です。LINE 154: ウィンドウ枠の固定。

3行目以降をスクロールしても1〜2行目の見出しが常に表示されるよう、ウィンドウ枠を固定しています。LINE 157: データ行書き込み関数の定義。1行分のデータをまとめてシートに書き込むための関数を定義しています。

値・書式・塗りつぶしを共通処理としてまとめています。LINE 158: 列ごとに値を取り出すループ。valuesリストの各値に対して、1から始まる列番号を割り当てながら処理を進めています。

LINE 159: セルへの値の書き込み。指定した行と列の位置に、対応する値を書き込んでセルオブジェクトを取得しています。LINE 160: データセルへの罫線設定。

書き込んだセルにCELL_BORDERの罫線を設定して、表としての区切りを見やすくしています。LINE 161: 数値書式が指定されているかの判定。formats辞書にその列番号が登録されているかどうかを確認しています。

登録されている列だけ表示形式を整えます。LINE 162: セルの表示形式を適用。該当する列の数値表示形式(カンマ区切りや円マークなど)をセルに設定しています。

LINE 163: 塗りつぶし指定の有無を確認。fill引数が渡されているかどうかを判定しています。強調表示が必要な行だけ色を付けるための分岐です。

LINE 164: セルへの塗りつぶし適用。指定された塗りつぶし色をセルに設定し、要注意な行などを目立たせています。LINE 168: 在庫一覧シート作成関数の定義。

在庫一覧シートに見出しと全明細データを書き込むための関数を定義しています。LINE 169: 在庫一覧の見出し項目を定義。在庫一覧シートに表示する列見出しの一覧をリストとして用意しています。

LINE 170: 見出し行の書き込み。共通関数を呼び出してタイトルと見出し行、各列の幅をシートに書き込んでいます。LINE 171: 数値列の表示形式を定義。

単価・在庫数・発注点・在庫金額の各列に適用する表示形式を辞書としてまとめています。LINE 172: 書き込み開始行の初期化。データの書き込みを開始する行番号を、見出しの次の行である3に設定しています。

LINE 173: 明細データを1件ずつ処理するループ。itemsリストの各在庫明細に対して、順番にシートへの書き込み処理を行っています。LINE 174: 見出し順に値を並べる。

見出しの並び順に合わせて、その商品の各項目の値を取り出しリストにしています。LINE 175: 要補充行の強調色を決定。判定が「要補充」であればALERT_FILLを、そうでなければ塗りつぶしなしを選んでいます。

LINE 176: 1行分のデータ書き込み。共通関数を使ってこの商品の値・書式・強調色をまとめてシートに書き込んでいます。LINE 177: 書き込み行番号の更新。

次の商品を書き込むために行番号を1つ進めています。LINE 178: オートフィルターの設定。見出し行から最終データ行までの範囲にオートフィルターを設定し、Excel上で並べ替えや絞り込みができるようにしています。

RUN 5/8: 在庫一覧シートの体裁を確認する。_build_items_sheetまで入力した状態です。ブックを1つ作って明細を書き込み、見出しや固定枠、オートフィルタが設定されるかを見ます。

CHECK 5/8: 途中実行に成功。シート名: 在庫一覧 / 最終行: 4 / 最終列: 8 A1: 在庫一覧(全明細) / A2: 商品コード オートフィルタ: A2:H4 / 固定枠: A3 G3の表示形式: #,##0 RETURN 05: エディターへ戻りました。入力済みのコードを維持したまま、次の実装へ進みます。

LINE 182: 在庫金額シート作成関数の定義。カテゴリ別の在庫金額集計をシートに書き込むための関数を定義しています。LINE 183: 在庫金額シートの見出し項目を定義。

カテゴリ・品目数・在庫数合計・在庫金額・構成比の見出しリストを用意しています。LINE 184: 見出し行の書き込み。共通関数を呼び出してタイトルと見出し、列幅をシートに設定しています。

LINE 185: 数値列の表示形式を定義。品目数・在庫数合計・在庫金額・構成比の各列に適用する表示形式を辞書にまとめています。LINE 186: 書き込み開始行の初期化。

集計データの書き込みを開始する行番号を3に設定しています。LINE 187: カテゴリ集計を1件ずつ処理するループ。summaryリストに含まれる各カテゴリの集計結果を順番に処理しています。

LINE 188: 表示用の値を並べる。カテゴリ名や品目数、在庫数、在庫金額、構成比の値を書き込み用のリストにまとめています。LINE 189: 1行分の集計データ書き込み。

共通関数を使ってこのカテゴリの集計値をシートに書き込んでいます。LINE 190: 書き込み行番号の更新。次のカテゴリを書き込むために行番号を1つ進めています。

LINE 191: 在庫数の全体合計を計算。全明細の在庫数を合計し、合計行に表示するための総在庫数を求めています。LINE 192: 合計行の値を組み立てる。

ラベル「合計」と品目総数・在庫数合計・在庫金額合計・構成比100%(または0)をまとめてリストにしています。LINE 193: 合計行の書き込み。組み立てた合計値をシートの最終行に書き込んでいます。

LINE 194: 合計行の各列を処理するループ。合計行の全列に対して、見た目を強調するための処理を順番に行っています。LINE 195: 合計行のセルを取得。

強調表示を適用する対象として、合計行のセルを取得しています。LINE 196: 合計行を太字にする。合計行の文字をTOTAL_FONTの太字にして、他の行と区別しやすくしています。

LINE 197: 合計行に二重罫線を設定。合計行の上側に二重線を含むTOTAL_BORDERを設定し、集計行であることを視覚的に示しています。LINE 201: 補充対象シート作成関数の定義。

在庫数が発注点以下の商品を抽出し、補充対象シートに書き込むための関数を定義しています。LINE 202: 補充対象シートの見出し項目を定義。商品コードから発注予定金額までの見出しリストを用意しています。

LINE 203: 見出し行の書き込み。共通関数を呼び出してタイトルと見出し、列幅をシートに設定しています。LINE 204: 数値列の表示形式を定義。

在庫数から発注予定金額までの各列に適用する表示形式を辞書にまとめています。LINE 205: 補充対象の抽出と並べ替え。判定が「要補充」の商品だけを抽出し、在庫の余裕が少ない順・単価が高い順に並べ替えています。

LINE 206: 要補充商品の絞り込み条件。itemsの中から判定が「要補充」である商品のみをリストに残す条件を指定しています。LINE 207: 並べ替えのキー指定。

在庫数と発注点の差が小さい順、次に単価が高い順になるよう並べ替えの基準を指定しています。LINE 208: 書き込み開始行の初期化。補充対象データの書き込みを開始する行番号を3に設定しています。

LINE 209: 発注金額合計の初期化。これから積み上げていく発注予定金額の合計を0で初期化しています。LINE 210: 発注数量合計の初期化。

これから積み上げていく補充推奨数の合計を0で初期化しています。LINE 211: 補充対象を1件ずつ処理するループ。並べ替え済みのtargetsリストから、補充対象の商品を順番に処理しています。

LINE 212: 発注予定金額の計算。その商品の補充推奨数に単価を掛けて、1商品あたりの発注予定金額を求めています。LINE 213: 発注金額合計への加算。

計算した発注予定金額を全体の合計に加算しています。LINE 214: 発注数量合計への加算。その商品の補充推奨数を全体の発注数量合計に加算しています。

LINE 215: 表示用の値リストの開始。シートに書き込む値のリストを組み立て始めています。LINE 216: 商品コードの追加。

表示する値リストに商品コードを加えています。LINE 217: 商品名の追加。表示する値リストに商品名を加えています。

LINE 218: カテゴリの追加。表示する値リストにカテゴリ名を加えています。LINE 219: 在庫数の追加。

表示する値リストに現在の在庫数を加えています。LINE 220: 発注点の追加。表示する値リストに発注点の値を加えています。

LINE 221: 不足数の追加。表示する値リストに発注点に対する不足数を加えています。LINE 222: 補充推奨数の追加。

表示する値リストに補充を推奨する数量を加えています。LINE 223: 補充推奨数リストの一部要素。補充推奨数の値を値リストに含める処理で、この後発注予定金額が続きます。

LINE 224: 発注予定金額をリストへ追加。補充推奨数と単価から計算した発注予定金額を、書き込み用の値リストの最後の要素として追加しています。この値がExcelの「発注予定金額」列にそのまま出力されます。

LINE 225: 書き込み値リストの終端。商品コードから発注予定金額までをまとめたリストの定義を閉じています。この1行分のデータがそのままシートの1行に書き込まれます。

LINE 226: 在庫切れ行の強調色を判定。在庫数が0の商品だけ強調用の背景色ALERT_FILLを設定し、それ以外の商品には色を付けないようにしています。在庫切れの行を目立たせるための処理です。

LINE 227: 補充対象の1行をシートへ書き込み。組み立てた値と書式、背景色を渡して、補充対象シートの現在の行にデータを書き込んでいます。共通関数_write_data_rowを再利用することで書き込み処理を統一しています。

LINE 228: 次の行番号へ進める。1件書き込むごとに行番号を1つ増やし、次の商品を次の行に書き込めるようにしています。これにより明細が上から順に並びます。

LINE 229: 補充対象が0件かどうかの判定。要補充の商品が1件も無かった場合の処理に分岐しています。空のシートにならないよう、この後で案内メッセージを表示します。

LINE 230: 補充対象なしのメッセージ表示。補充対象が無いことをシート上に文字で示し、太字のフォントを設定して見やすくしています。利用者が空欄と誤解しないようにする配慮です。

LINE 231: 件数と金額を0として返す。補充対象が無い場合は件数0と発注予定金額0のタプルを呼び出し元に返し、以降の集計処理をここで終えています。LINE 232: 合計行の値リストを作成。

数値以外の列は空文字にし、数量と金額の列だけに合計値order_quantityとorder_amountを入れた「合計」行のリストを作っています。LINE 233: 合計行をシートへ書き込み。作成した合計値のリストを、明細の続きの行として補充対象シートに書き込んでいます。

LINE 234: 合計行の各列を装飾するループ。見出しの列数分だけループし、合計行のすべてのセルに対して同じ装飾処理を適用しようとしています。LINE 235: 装飾対象のセルを取得。

合計行・現在の列番号にあるセルを取得し、次の行でフォントや罫線を設定できるようにしています。LINE 236: 合計行を太字にする。取得したセルに太字フォントを設定し、合計行が明細行と見分けやすくなるようにしています。

LINE 237: 合計行に二重罫線を設定。合計行の上側に二重線を含む罫線TOTAL_BORDERを設定し、集計行であることを視覚的に強調しています。LINE 238: 件数と発注予定金額を返す。

補充対象の件数と発注予定金額の合計を呼び出し元に返し、build関数側でのメッセージ表示に使えるようにしています。RUN 6/8: 補充対象シートの集計値を受け取る。_build_reorder_sheetまで入力した状態です。

要補充の明細を渡し、対象件数と発注予定金額、合計行の内容を表示します。CHECK 6/8: 途中実行に成功。

補充対象: 1件 / 発注予定金額: 3600円 A1: 補充対象(在庫数が発注点以下) 明細行: ['SKU-1001', 'リングノートA5', '文具', 0, 10, 10, 20, 3600] 合計行: ['合計', '', '', '', '', '', 20, 3600] RETURN 06: エディターへ戻りました。

入力済みのコードを維持したまま、次の実装へ進みます。LINE 242: シート読み取り関数の定義。ワークシートから指定した行数分だけ値を取り出す関数_read_rowsを定義しています。

帳票の内容確認機能で使う共通処理です。LINE 243: 読み取り結果を貯める空リスト。取得した各行のデータを順番に格納していくための空のリストrowsを用意しています。

LINE 244: シートの全行を順番に取得。ワークシートのすべての行を、値だけのタプルとして1行ずつ取り出すループを開始しています。LINE 245: 取得した行をリストへ追加。

読み取った1行分のデータをrowsリストの末尾に追加し、後で利用できるように蓄積しています。LINE 246: 取得済み行数の上限チェック。すでに取得した行数が指定された上限max_rowsに達したかどうかを確認しています。

上限を超えた読み込みを防ぐための判定です。LINE 247: 上限到達時にループを打ち切り。上限に達した場合はそれ以上行を読み込まず、ループを抜けて処理を終わらせています。

大きな帳票でも読み込み量を制限できます。LINE 248: 読み取った行リストを返す。これまでに集めた行データのリストをそのまま呼び出し元に返し、内容確認の表示処理で使えるようにしています。

LINE 251: セル値を表示用文字列に変換する関数の定義。Excelのセルの値を種類に応じて見やすい文字列に変換する関数_format_cellを定義しています。数値や割合、空欄などをまとめて扱うための関数です。

LINE 252: 値が空かどうかの判定。セルの値がNone、つまり空欄かどうかを確認しています。LINE 253: 空欄を空文字として返す。

値がNoneの場合は表示上も空の文字列を返し、何も表示されないようにしています。LINE 254: 小数値かどうかの判定。値がfloat型かどうかを確認し、構成比のようなパーセント表示が必要な値かどうかを見分けています。

LINE 255: 小数値をパーセント表示に整形。float型の値を100倍したうえで小数点1桁のパーセント文字列に整形して返しています。構成比などを読みやすい形式に変換する処理です。

LINE 256: 整数値かどうかの判定。値がint型かどうかを確認し、金額や数量のようにカンマ区切りで表示したい値かどうかを見分けています。LINE 257: 整数値をカンマ区切りに整形。

int型の値を3桁ごとのカンマ区切り文字列に整形して返し、金額や数量を読みやすく表示できるようにしています。LINE 258: その他の値をそのまま文字列化。上記のいずれにも当てはまらない値は、そのまま文字列に変換して返しています。

商品名やカテゴリなどの文字列項目に対応します。LINE 262: sample-csvコマンドの登録。この関数をTyperの「sample-csv」というサブコマンドとして登録しています。

コマンドライン上でこの名前を指定すると下の関数が実行されます。LINE 263: サンプルCSV作成関数の定義開始。サンプルの在庫CSVを作成するsample_csv関数の定義を開始し、以降の引数を受け取れるようにしています。

LINE 264: 出力先パスのオプション定義。作成するCSVファイルの出力先を指定するoutputオプションを定義し、指定が無い場合はinventory.csvを既定値としています。LINE 265: 生成行数のオプション定義。

サンプルデータとして何行作るかを指定するrowsオプションを定義し、1〜200件の範囲、既定値24件で受け付けています。LINE 266: 乱数シードのオプション定義。サンプルデータ生成に使う乱数シードを指定するseedオプションを定義しています。

同じシードを指定すれば同じデータが再現できます。LINE 267: 関数の引数定義部分の終了。sample_csv関数のオプション引数の定義を閉じ、戻り値が無いことを示しています。

LINE 268: コマンドの説明文。このコマンドがどのような動作をするかを説明するdocstringで、CLIのヘルプ表示にも利用されます。LINE 269: サンプル明細データの生成。

指定された行数と乱数シードをもとに、_build_sample_rows関数でサンプルの在庫明細データを組み立てています。LINE 270: 出力先フォルダの作成。出力ファイルの保存先フォルダが存在しない場合に自動で作成し、存在していてもエラーにならないようにしています。

LINE 271: CSVファイルを書き込みモードで開く。指定された出力先にBOM付きUTF-8でCSVファイルを新規に開き、以降の処理でこのファイルへ書き込めるようにしています。LINE 272: CSV書き込み用オブジェクトの作成。

辞書形式のデータをCSVとして書き出すためのDictWriterを、決められた列名CSV_FIELDSの順序で作成しています。LINE 273: CSVのヘッダー行を書き込み。CSVファイルの1行目に商品コードなどの列名を書き込み、以降のデータ行の意味が分かるようにしています。

LINE 274: サンプルデータをCSVへ書き込み。生成したすべてのサンプル明細レコードを、順番にCSVファイルへ書き込んでいます。LINE 275: 要補充件数の集計。

生成したサンプルデータのうち、在庫数が発注点以下となっている件数を数え、変数lowに保存しています。LINE 276: 使用カテゴリ一覧の作成。サンプルデータに含まれるカテゴリを重複無しで集めて並べ替え、後のメッセージ表示で使えるようにしています。

LINE 277: 作成完了メッセージの表示。CSVファイルを作成したことと、その保存先パスを画面に表示しています。LINE 278: 件数とカテゴリのメッセージ表示。

生成した明細の件数と、含まれるカテゴリの一覧を画面に表示しています。LINE 279: 要補充件数のメッセージ表示。在庫数が発注点以下となっている行数を画面に表示し、サンプルデータの内容を確認しやすくしています。

LINE 280: プレビュー見出しの表示。これから先頭数行のデータを表示することを示す案内メッセージを画面に出しています。LINE 281: CSV列名のプレビュー表示。

CSVの列名一覧を「|」区切りの1行としてまとめ、画面に表示しています。LINE 282: 先頭3件のループ処理。生成したレコードの先頭3件だけを取り出し、順番に処理するループを開始しています。

LINE 283: レコード内容のプレビュー表示。各レコードの値を列名の順に並べ、「|」区切りの1行として画面に表示しています。作成したCSVの中身を確認できるようにする処理です。

RUN 7/8: sample-csvの処理だけを動かす。sample-csvコマンドの関数まで入力した状態です。出力先と件数を指定して呼び出し、CSVの作成メッセージを確認します。

CHECK 7/8: 途中実行に成功。

サンプルCSVを作成しました: /tmp/pylesson_rq0wa2so/checkpoint/inventory.csv 明細行数: 8件 / カテゴリ: 事務用品, 文具 うち在庫数が発注点以下の行: 2件 先頭3行のプレビュー: 商品コード | 商品名 | カテゴリ | 単価 | 在庫数 | 発注点 SKU-1001 | 油性ボールペン0.7mm | 文具 | 128 | 32 | 15 SKU-1002 | リングノートA5 | 文具 | 1980 | 7 | 5 SKU-1003 | 蛍光マーカー5色セット | 文具 | 3480 | 71 | 30 CSVの存在: True RETURN 07: エディターへ戻りました。

入力済みのコードを維持したまま、次の実装へ進みます。LINE 287: buildコマンドの登録。この関数をTyperの「build」というサブコマンドとして登録し、コマンドライン実行時に呼び出せるようにしています。

LINE 288: 帳票作成関数の定義開始。CSVからExcel帳票を作成するbuild関数の定義を開始し、以降の引数を受け取れるようにしています。LINE 289: 入力CSVパスの引数定義。

帳票作成に使う入力CSVファイルのパスを必須の位置引数として定義し、存在するファイルであることなどを検証しています。LINE 290: 出力先パスのオプション定義。生成するExcel帳票の保存先を指定するoutputオプションを定義し、既定値としてinventory_report.xlsxを設定しています。

LINE 291: 関数の引数定義部分の終了。build関数の引数定義を閉じ、戻り値が無いことを示しています。LINE 292: コマンドの説明文。

このコマンドが在庫CSVから3シート構成のExcel帳票を作ることを説明するdocstringです。LINE 293: CSVから在庫データを読み込み。_load_items関数を使って入力CSVを読み込み、単価や在庫金額などを計算済みの明細データitemsを取得しています。

LINE 294: カテゴリ別集計の実行。読み込んだ明細データを_summarize_by_category関数に渡し、カテゴリごとの集計結果summaryと在庫金額の総合計total_amountを取得しています。LINE 296: 新しいExcelブックの作成。

openpyxlのWorkbookクラスを使って、新規のExcelブックオブジェクトbookを作成しています。この後、この中に複数のシートを追加していきます。LINE 297: 既定シートの取得。

新規ブックに最初から用意されている既定のシートをws_itemsという変数として取得し、在庫一覧シートとして使えるようにしています。LINE 298: シート名を在庫一覧に設定。取得した既定シートの名前を、共通設定で定めたSHEET_ITEMS(在庫一覧)に変更しています。

LINE 299: 在庫一覧シートの内容を構築。_build_items_sheet関数を呼び出し、在庫一覧シートに見出しや全明細データを書き込んでいます。LINE 300: 在庫金額シートの作成。

新しく作成した「在庫金額」シートに対して_build_amount_sheet関数を呼び出し、カテゴリ別の集計結果を書き込みます。book.create_sheetで新規シートを追加しながら、そのまま関数の引数として渡している点がポイントです。LINE 301: 補充対象シートの作成と結果の受け取り。

「補充対象」シートを新規作成して_build_reorder_sheetに渡し、返ってきた補充対象の件数と発注予定金額をreorder_countとorder_amountに受け取ります。この戻り値は後の画面出力で使われます。LINE 303: 出力先フォルダの作成。

帳票の出力先パスの親フォルダが存在しない場合に、自動で作成します。exist_ok=Trueにより、既にフォルダがあってもエラーにならず処理を続けられます。LINE 304: Excelファイルの保存。

ここまで組み立ててきたWorkbookオブジェクトをoutputで指定されたパスにExcelファイルとして保存します。この行の実行によって実際にファイルがディスク上に作成されます。LINE 306: 在庫数合計の計算。

itemsリストの各明細から「在庫数」を取り出して合計し、全体の在庫数合計をtotal_stockに求めます。次の画面出力メッセージで使うための集計です。LINE 307: 作成完了メッセージの表示。

帳票の保存が完了したことを知らせるメッセージを画面に表示します。output.resolve()で絶対パスに変換して表示するため、実際にどこにファイルができたかが分かりやすくなります。LINE 308: 入力CSVと明細件数の表示。

読み込んだ元のCSVファイルのパスと、処理した明細の件数を画面に表示します。len(items)でリストの要素数を数えて件数として表示しています。LINE 309: 在庫数と在庫金額合計の表示。

先ほど計算したtotal_stockと在庫金額の合計であるtotal_amountをカンマ区切りの数値としてまとめて表示します。{:,}の書式指定により3桁ごとにカンマが入り読みやすくなります。LINE 310: 補充対象件数と発注金額の表示。

_build_reorder_sheetから受け取ったreorder_countとorder_amountを使って、補充が必要な品目数と発注予定金額をまとめて表示します。LINE 311: シート名一覧の表示。book.sheetnamesで作成したブック内の全シート名を取得し、区切り文字「|」でつなげて一覧表示します。

3つのシートが正しく作られたことを確認できます。RUN 8/8: buildで帳票の生成まで通す。buildコマンドの関数まで入力した状態です。

サンプルCSVを作ってから帳票を生成し、集計結果の表示とファイルの作成を確かめます。CHECK 8/8: 途中実行に成功。

サンプルCSVを作成しました: /tmp/pylesson_1_2ghwvs/checkpoint/inventory.csv 明細行数: 12件 / カテゴリ: PC周辺, 事務用品, 文具 うち在庫数が発注点以下の行: 3件 先頭3行のプレビュー: 商品コード | 商品名 | カテゴリ | 単価 | 在庫数 | 発注点 SKU-1001 | 油性ボールペン0.7mm | 文具 | 128 | 32 | 15 SKU-1002 | リングノートA5 | 文具 | 1980 | 7 | 5 SKU-1003 | 蛍光マーカー5色セット | 文具 | 3480 | 71 | 30 Excel帳票を作成しました: /tmp/pylesson_1_2ghwvs/checkpoint/report.xlsx 入力CSV: checkpoint/inventory.csv / 明細件数: 12件 在庫数合計: 379点 / 在庫金額合計: 490,184円 補充対象: 3件 / 発注予定金額: 8,192円 シート: 在庫一覧 | 在庫金額 | 補充対象 帳票の存在: True RETURN 08: エディターへ戻りました。

入力済みのコードを維持したまま、次の実装へ進みます。LINE 315: inspectコマンドの定義。typerのデコレーターを使い、コマンドライン上で「inspect」というサブコマンドとして次に定義する関数を登録します。

これによりCLIから帳票の中身を確認できるようになります。LINE 316: inspect_report関数の定義開始。生成済みのExcel帳票を読み取って内容を表示するinspect_report関数の定義を始めます。

この関数がinspectコマンドの実処理を担います。LINE 317: 確認対象の帳票パスを受け取る引数。コマンドライン引数として、内容を確認したい帳票ファイルのパスを受け取ります。

existsやreadableのチェックにより、存在しないファイルや読み取れないファイルが指定された場合はエラーになります。LINE 318: 表示対象シートを指定するオプション。--sheetまたは-sオプションで、内容を表示したいシート名を指定できるようにします。

省略した場合はNoneとなり、後の処理で全シートを対象にする分岐に使われます。LINE 319: 表示件数の上限を指定するオプション。--limitまたは-lオプションで、明細を何行まで表示するかを指定できるようにします。

デフォルトは8行で、1から100の範囲に収まるよう制限されています。LINE 320: 関数の戻り値の型宣言。この関数が値を返さないことを示す型ヒントです。

inspect_reportは画面表示のみを行い、戻り値を持たない処理として定義されています。LINE 321: 関数の説明文(docstring)。この関数が生成済みのExcel帳票を読み取り、シート構成や明細を表示することを説明する文章です。

typerのヘルプ表示にも利用されます。LINE 322: Excelファイルの読み込み。指定されたパスのExcelファイルをload_workbookで開きます。

data_only=Trueで数式ではなく計算済みの値を取得し、read_only=Trueによりメモリ効率よく読み込みます。LINE 323: tryブロックの開始。ファイルを開いた後の処理をtryブロックで囲み、途中でエラーが発生してもfinallyでファイルを確実に閉じられるようにしています。

LINE 324: 指定シート名の存在確認。sheetオプションが指定されており、かつそのシート名がブック内に存在しない場合の条件を判定します。誤ったシート名を防ぐためのチェックです。

LINE 325: シートが見つからない場合のエラーメッセージ。指定されたシート名が見つからない場合に、存在するシート名一覧とともにエラーメッセージを表示します。ユーザーが正しいシート名を再指定できるようにするための案内です。

LINE 326: エラー終了処理。シートが見つからないという異常な状態のため、終了コード1でプログラムを終了させます。これによりコマンドラインからエラーが発生したことを判別できます。

LINE 327: 帳票パスの表示。確認対象となっている帳票ファイルの絶対パスを画面に表示します。どのファイルを読み込んでいるのかを利用者に明示するための行です。

LINE 328: シート数と一覧の表示。ブックに含まれるシートの数と、それぞれのシート名を一覧表示します。len(book.sheetnames)でシート数を数え、joinで名前をつなげています。

LINE 329: 表示対象シートのリスト作成。sheetが指定されていればそのシートだけを、指定されていなければ全シートをtargetsというリストにまとめます。この後のループ処理で使う対象を決めています。

LINE 330: 明細表示行数の決定。シートが指定されている場合のみlimitで指定された行数を使い、指定されていない場合は0にすることで明細を表示しないようにしています。全シート表示時は見出しのみにとどめる工夫です。

LINE 331: 対象シートを1つずつ処理するループ。targetsに含まれる各シート名について、順番に内容を読み取って表示する処理を行うループの開始です。LINE 332: シートオブジェクトの取得。

book[name]でシート名からそのシートのオブジェクトを取得します。以降の行で、このシートの内容を読み取ります。LINE 333: シート内容の読み取り。

_read_rows関数を使い、タイトル行と見出し行に加え、body_limit分の明細行を含めた行数分のデータを読み取ります。読み取った内容はrowsに格納されます。LINE 334: タイトル文字列の取得。

rowsが存在する場合、先頭行の1列目のセル値を_format_cellで整形してtitle_textに格納します。rowsが空の場合は空文字列にしています。LINE 335: 見出し行の取得。

rowsが2行以上ある場合、2行目にあたる見出し行の各セル値を_format_cellで整形してheadersリストに格納します。見出しが取得できない場合は空リストになります。LINE 336: 表示の区切りとなる空行。

シートごとの表示を見やすくするため、空の行を出力して前後の内容を区切っています。LINE 337: シート概要の表示。シート名・タイトル・行数・列数をまとめて画面に表示します。

ws.max_rowやws.max_columnで実際のデータ範囲を取得し、値が無い場合は0として扱っています。LINE 338: 見出し行の表示。取得したheadersリストの各項目を「|」区切りでつなげて、見出し部分として画面に表示します。

LINE 339: 明細行を1件ずつ処理するループ。rowsの3行目以降、すなわちタイトルと見出しを除いた明細データ部分を1行ずつ取り出して処理するループです。LINE 340: 明細行の表示。

1行分のデータに含まれる各セル値を_format_cellで整形し、「|」区切りでつなげて画面に表示します。これにより明細内容が見やすい形で出力されます。LINE 341: 全シート表示時の案内表示条件。

sheetオプションが指定されていない、つまり全シートをまとめて表示した場合にのみ次の案内メッセージを表示するための条件分岐です。LINE 342: 案内表示前の空行。次に表示する案内メッセージを見やすくするため、区切りとなる空行を出力しています。

LINE 343: 詳細表示のための操作案内。明細まで詳しく見たい場合は--sheetと--limitオプションを指定するよう促すメッセージを表示します。全シート表示では明細が省略されていることをユーザーに伝えます。

LINE 344: finallyブロックの開始。tryブロックで例外が発生してもしなくても必ず実行される後片付け処理の開始を示します。LINE 345: ブックのクローズ処理。

開いていたExcelファイルのブックオブジェクトを閉じ、ファイルリソースを解放します。read_onlyモードで開いたファイルを適切に閉じるために必要な処理です。LINE 348: main関数の定義。

プログラムのエントリーポイントとなるmain関数を定義しています。この関数はスクリプトが直接実行されたときに呼び出されます。LINE 349: Typerアプリの起動。

typerで構築したappオブジェクトを呼び出し、コマンドライン引数を解析してsample-csvやbuild、inspectなどのコマンドを実行します。LINE 352: 直接実行時の判定。このスクリプトファイルが他のモジュールとしてインポートされたのではなく、直接実行されたかどうかを判定する定番の書き方です。

LINE 353: main関数の呼び出し。直接実行された場合にmain関数を呼び出し、実際にTyperアプリを起動してコマンドライン処理を開始します。実行1/5: サンプルの在庫CSVを作成する。

sample-csvコマンドで動作確認用の在庫データを作ります。明細行数とカテゴリ、先頭3行のプレビューが表示されます。確認1/5: サンプルの在庫CSVを作成する。

sample-csvコマンドで動作確認用の在庫データを作ります。明細行数とカテゴリ、先頭3行のプレビューが表示されます。RETURN 09: エディターへ戻りました。

入力済みのコードを維持したまま、次の実装へ進みます。実行2/5: 在庫CSVから3シートの帳票を生成する。作成したCSVをbuildコマンドへ渡し、在庫一覧・在庫金額・補充対象の3シートを持つExcel帳票を書き出します。

確認2/5: 在庫CSVから3シートの帳票を生成する。作成したCSVをbuildコマンドへ渡し、在庫一覧・在庫金額・補充対象の3シートを持つExcel帳票を書き出します。RETURN 10: エディターへ戻りました。

入力済みのコードを維持したまま、次の実装へ進みます。実行3/5: 生成した帳票のシート構成を確認する。inspectコマンドをシート名なしで実行し、シート数と各シートの題名や見出し、表の大きさを一覧表示します。

確認3/5: 生成した帳票のシート構成を確認する。inspectコマンドをシート名なしで実行し、シート数と各シートの題名や見出し、表の大きさを一覧表示します。RETURN 11: エディターへ戻りました。

入力済みのコードを維持したまま、次の実装へ進みます。実行4/5: 在庫金額シートのカテゴリ別集計を表示する。--sheetで在庫金額シートを指定し、カテゴリ別の品目数や在庫金額、構成比と合計行を読み返します。

確認4/5: 在庫金額シートのカテゴリ別集計を表示する。--sheetで在庫金額シートを指定し、カテゴリ別の品目数や在庫金額、構成比と合計行を読み返します。RETURN 12: エディターへ戻りました。

入力済みのコードを維持したまま、次の実装へ進みます。実行5/5: 補充対象シートの明細を表示する。--sheetと--limitで補充対象シートを絞り込み、発注候補の見出しと明細をターミナルで確認します。

確認5/5: 補充対象シートの明細を表示する。--sheetと--limitで補充対象シートを絞り込み、発注候補の見出しと明細をターミナルで確認します。RETURN 13: エディターへ戻りました。

入力済みのコードを維持したまま、次の実装へ進みます。学習内容のまとめ。Workbook()で空のブックを作成typer.Typerでアプリ本体を作成 在庫金額は単価と在庫数の積CSVをutf-8-sigで読み書きしてBOM付きにも対応する 小さく実行確認しながら完成状態まで段階的に組み立てる エンディング。

Python研修はCodeCampでご確認ください。

PythonのopenpyxlとTyperとは

今回使用する主要なライブラリについて、役割と使い分けを順番に確認します。

Excel帳票を組み立てるopenpyxlの役割

openpyxlは、Excelファイルの作成と読み取りをPythonから直接行えるライブラリです。Workbookでブックを新規に組み立て、シートの追加やセルへの値の書き込み、フォント・塗りつぶし・罫線・表示形式といった見た目の指定までコードだけで完結できます。

今回の在庫報告書作成CLIでは3枚のシートを作り分け、見出し行の色分けや金額の桁区切り、要補充行の強調をopenpyxlで組み立てました。保存した帳票を読み返す場面ではload_workbookを使い、値だけを取り出して端末へ並べています。

在庫報告書作成CLIのコードでopenpyxlが担当している処理を、実際に呼び出している機能ごとに並べます

  • Workbook()で空のブックを作成
  • book.activeの1枚目を在庫一覧へ改名
  • create_sheetで在庫金額と補充対象を追加
  • PatternFillのfgColor=1F4E78で見出し行を濃紺に塗る
  • number_formatの#,##0で金額と数量を桁区切り
  • freeze_panesのA3で見出しまでを固定
  • column_dimensionsとget_column_letterで列幅を指定
  • auto_filter.refで在庫一覧にフィルタを設定
  • load_workbookのdata_only指定で保存時の値を読む

コマンドを組み立てるTyperの役割

Typerは、Pythonの関数をそのままコマンドとして公開できるCLIライブラリです。引数に型注釈とtyper.Argumentやtyper.Optionを添えるだけで、--outputのような選択肢やヘルプ文、値の検証が自動で用意されます。

今回は1つのアプリへ3つのコマンドを登録し、sample-csvでデータ作成、buildで帳票生成、inspectで内容確認という役割分担にしました。実行結果はtyper.echoで標準出力へ流し、入力の不備はtyper.BadParameterで利用者向けのメッセージへ変えています。

在庫報告書作成CLIでTyperに任せているコマンド定義の要素を挙げます

  • typer.Typerでアプリ本体を作成
  • app.commandへsample-csvなどの公開名を割り当てる
  • 関数のdocstringがコマンドのヘルプ文になる
  • typer.Argumentのexists=Trueで入力ファイルを検査
  • typer.Optionのmin=1とmax=200で明細行数を制限
  • Optional型の--sheetで省略可能な絞り込みを受け取る
  • -oや-nなどの短縮形を併記
  • typer.echoで実行結果を1行ずつ表示
  • typer.Exitのcode=1で処理を打ち切る
さらに詳しく学べるPython研修の詳細はこちら

Python・openpyxlで開発する場合の環境構築

この記事のセットアップ手順と掲載コードは、Windows 11 Pro、PowerShell 5.1、Python 3.13.3で動作確認しています。仮想環境を有効化せず、その中のPythonを直接指定するため、以下のコマンドはPowerShellとコマンドプロンプト(cmd)の両方で使えます。

python -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pip
.\.venv\Scripts\python.exe -m pip install openpyxl typer

macOS・Linuxでは仮想環境内のPythonパスが異なります。今回の動作確認環境とは異なるため、以下は環境差分を補う参考手順です。

python3 -m venv .venv
./.venv/bin/python -m pip install --upgrade pip
./.venv/bin/python -m pip install openpyxl typer
  • typerを入れるとCLI解析に使うclickも一緒に導入されます。追加でclickを個別に入れる必要はありません。
  • openpyxlは.xlsx専用です。古い.xls形式のファイルは読み書きできないため、事前に.xlsxへ変換してください。
さらに詳しく学べるPython研修の詳細はこちら

在庫報告書作成CLIの要件定義

目的は、CSVの在庫データからExcelの在庫報告書を自動生成し、在庫一覧・在庫金額・補充対象の3シートと補充候補の集計をコマンド操作だけで確認できるようにすることです。

対象者として、Pythonの基本文法を一通り学び、CSVの集計やExcel帳票の自動生成をコマンドラインツールとして形にする方法を学びたい人を想定しています。

完成物は、openpyxlとTyperで作る在庫報告書作成CLIツールで、sample-csvによる試用データ作成、buildによる3シート帳票生成、inspectによる帳票内容の読み返しまでを1本のスクリプトで扱えるものです。

実装へ入る前に、機能・品質・受け入れ条件を分けて確認します。

機能要件

  • sample-csvコマンドで動作確認用の在庫CSVを出力する
  • --rowsと--seedで明細行数と乱数シードを指定する
  • 商品コードから発注点までの必須6列をそろえて書き出す
  • buildコマンドで在庫CSVを読み込んで明細を組み立てる
  • 在庫数が発注点以下の行へ要補充の判定を付ける
  • 明細ごとに在庫金額と不足数と補充推奨数を計算する
  • カテゴリ別の品目数と在庫数と在庫金額と構成比を集計する
  • 在庫一覧・在庫金額・補充対象の3シートを持つブックを保存する
  • 在庫金額シートの末尾へ合計行を追加する
  • 補充対象シートを在庫の逼迫度順に並べ替える
  • inspectコマンドで保存済み帳票のシート構成を表示する
  • --sheetと--limitで指定シートの明細を行数付きで表示する
  • 実行後に明細件数や在庫金額合計をターミナルへ表示する

非機能要件

  • CSVをutf-8-sigで読み書きしてBOM付きにも対応する
  • 必須列が欠けたCSVをBadParameterで中断する
  • 数値へ変換できない行を行番号付きのエラーで中断する
  • 明細行が0件のCSVを受け付けない
  • --rowsをmin=1・max=200の範囲に制限する
  • --limitをmin=1・max=100の範囲に制限する
  • buildとinspectの引数へexists=Trueとdir_okay=Falseを指定する
  • 乱数シードの固定で同じサンプルデータを再現する
  • 出力先の親ディレクトリをmkdirで自動作成する
  • 存在しないシート名の指定を終了コード1で打ち切る
  • inspectをread_only・data_onlyで開いてfinallyで閉じる
  • add_completion=Falseで補完用オプションを表示しない
  • フォントと塗りつぶしと表示形式をモジュール定数で共通化する

実装方針

今回はopenpyxlとTyperの基本動作を追いやすくするため、在庫報告書作成CLI本体を1つのPythonファイルへまとめます。

入力、判定、結果表示の役割を分け、実行結果を確認しながら機能を積み上げます。

在庫報告書作成CLIを安全に組み立てるための実装方針は次のとおりです。

  • CSVをutf-8-sigで読み書きしてBOM付きにも対応する
  • 必須列が欠けたCSVをBadParameterで中断する
  • 数値へ変換できない行を行番号付きのエラーで中断する
  • 明細行が0件のCSVを受け付けない
  • --rowsをmin=1・max=200の範囲に制限する
  • --limitをmin=1・max=100の範囲に制限する
  • buildとinspectの引数へexists=Trueとdir_okay=Falseを指定する
  • 乱数シードの固定で同じサンプルデータを再現する
  • 出力先の親ディレクトリをmkdirで自動作成する
  • 存在しないシート名の指定を終了コード1で打ち切る
  • inspectをread_only・data_onlyで開いてfinallyで閉じる
  • add_completion=Falseで補完用オプションを表示しない
  • フォントと塗りつぶしと表示形式をモジュール定数で共通化する

完成と判断する条件

  1. sample-csvの実行でCSVが作られ明細行数が表示される
  2. サンプルCSVの先頭3行がプレビュー表示される
  3. buildの実行でxlsxが保存されシート3枚の名前が並ぶ
  4. 在庫数合計と在庫金額合計が桁区切りで表示される
  5. 補充対象件数と発注予定金額が表示される
  6. inspectの実行でシート数3と各シートの行数と列数が出る
  7. --sheetの指定でそのシートの見出しと明細が表示される
  8. 存在しないシート名の指定でエラー表示のまま終了する
さらに詳しく学べるPython研修の詳細はこちら

openpyxlとTyperで在庫報告書作成CLIを作る際の重要ポイント

このツールの中身は、CSVを明細のリストへ読み替える工程と、その明細をシートへ書き分ける工程に分かれます。読み込みの時点で在庫金額や不足数、補充推奨数まで計算し、在庫数が発注点以下の行には要補充という判定を付けておきます。あとはカテゴリ別の集計を作り、在庫一覧・在庫金額・補充対象の3シートへ同じ明細を別の切り口で並べるだけです。

集計結果は帳票へ書き込むほか、実行直後のターミナルにも要約として表示されます。

明細1件ごとに決まる値と、各シートの並び順を決めている基準です

  • 在庫金額は単価と在庫数の積
  • 判定は在庫数が発注点以下なら要補充
  • 不足数は発注点から在庫数を引いた値で下限は0
  • 補充推奨数は発注点の2倍から在庫数を引いた値
  • 在庫金額シートはカテゴリ別在庫金額の降順
  • 構成比はカテゴリ別在庫金額を総額で割った比率
  • 補充対象シートは在庫数と発注点の差が小さい順
  • 差が同じ場合は単価の高い順
  • 在庫数が0の補充対象行を淡い赤で強調
  • 補充対象が0件なら不在を伝える一文を記載

CSVを明細へ読み替える_load_itemsの処理

_load_itemsはcsv.DictReaderでCSVを1行ずつ辞書として受け取ります。最初に必須6列がそろっているかを確かめ、欠けている列名を並べてエラーにするため、列名の打ち間違いにすぐ気づけます。単価と在庫数と発注点はintで整数へ直し、変換できない行があれば何行目かを添えて中断する作りです。

読み込みと同時に在庫金額や補充推奨数まで求めておくので、後続のシート作成は値を並べるだけで済みます。

読み込み時に明細1件へ追加している計算済みの項目です

  • 単価かける在庫数の在庫金額
  • 発注点から在庫数を引いた不足数
  • 発注点2倍から在庫数を引いた補充推奨数
  • 要補充か適正かの判定
  • utf-8-sigで開いてBOM付きCSVにも対応

カテゴリ別に集計する_summarize_by_category

_summarize_by_categoryは、明細をカテゴリ名で束ね、品目数と在庫数と在庫金額を足し上げます。集計後は在庫金額の大きい順へ並べ替え、全体合計に対する構成比も同時に持たせました。合計が0のときは割り算を避けて構成比を0.0とするため、在庫が空でも計算が止まりません。

戻り値はカテゴリ別の一覧と総額の2つで、在庫金額シートの明細行と合計行の両方に使われます。

在庫金額シートへ並ぶ列と合計行の作り方です

  • カテゴリごとの品目数を数える
  • カテゴリごとの在庫数を合計
  • カテゴリごとの在庫金額を合計
  • 構成比は在庫金額を総額で割った小数
  • 合計行へ明細件数と在庫数合計と総額を表示
  • 合計行の構成比は1.0

3枚のシートを書き分ける組み立て関数

シートごとの題名や見出し、列幅は_write_sheet_headにまとめ、明細1行の書き込みは_write_data_rowが受け持ちます。在庫一覧は全明細をそのまま並べ、要補充の行だけ背景色を変えたうえでオートフィルタを設定しました。在庫金額はカテゴリ別の集計と合計行、補充対象は在庫の逼迫した順に並べた発注候補という構成です。

補充対象が1件も無い場合は明細の代わりに一文を書き込み、空のシートにならないよう配慮しています。

3枚のシートがそれぞれ持つ列と特徴です

  • 在庫一覧は8列で全明細と判定を掲載
  • 在庫金額は5列でカテゴリ別集計と合計行
  • 補充対象は8列で発注予定金額まで算出
  • 共通の見出し行は2行目でA3から固定
  • 合計行だけ上辺を二重罫線に変更

帳票を読み返すinspectの表示処理

inspectはシート名を省略すると各シートの題名と見出し、行数と列数だけを並べ、--sheetを付けたときにその明細を--limitの行数まで表示します。読み取った値は_format_cellで整形し、整数は桁区切り、小数は百分率の表記へ直してから出力します。値の入っていないセルは空文字へ置き換えるので、表示が崩れることもありません。

Excelを開かずにターミナルだけで中身を確かめられるのが便利な点です。

inspectの表示で確認できる帳票の情報です

  • シート数と3枚のシート名
  • 各シートの題名と見出し行
  • シートの行数と列数
  • --sheet指定時の明細行
  • --limitで指定した最大表示行数

Python研修一覧はこちら

目的に合うPython研修を一覧形式から探したい方は、ぜひご利用ください。

Python研修を比較する

Java研修一覧はこちら

目的に合うJava研修を一覧形式から探したい方は、ぜひご利用ください。

Java研修を比較する

PHP研修一覧はこちら

目的に合うPHP研修を一覧形式から探したい方は、ぜひご利用ください。

PHP研修を比較する

新入社員研修

目的に合う新入社員研修を一覧形式から探したい方は、ぜひご利用ください。

新入社員研修を比較する

全ての研修からも探したい方はこちら

Pythonで在庫報告書作成CLIの完成コード

スクリプトは1ファイル構成です。先頭でcsvopenpyxlを読み込み、続けてCSVの列名・シート名・書式の定数を並べ、最後にTyperのコマンド関数を置いています。

書式を定数にまとめたのは、3枚のシートで同じ見出しデザインを使い回すためでした。色や罫線を変えたくなったときも、定数を1行直せば全シートに反映されます。

ここからは要点だけを抜き出し、断片ごとに何をしているのかを見ていきましょう。

このセクションの用語

Workbook
openpyxlで新しいExcelブックを表すオブジェクトです。ここにシートを追加し、セルへ値を書き込んで保存します。
PatternFill
セルの背景色を塗るためのopenpyxlの指定です。fgColorにRGBの16進コードを渡して色を決めます。
乱数シード
乱数の出発点になる数値です。同じ値を指定すれば同じ並びの乱数が得られるので、サンプルデータを再現できます。
CSVからxlsxまでのデータの流れ sample-csvで在庫CSVを作る_load_itemsで辞書のリストに変換単価×在庫数で在庫金額を計算Workbookに3シートを書き込むinspectでxlsxの値を読み戻す
CSVからxlsxまでのデータの流れ
"""openpyxlとTyperで作る在庫報告書作成ツール。

CSVの在庫データから、在庫一覧・在庫金額・補充対象の3シートを持つExcel帳票を生成する。
"""

import csv
import random
from pathlib import Path
from typing import Optional

import typer
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.utils import get_column_letter

# 共通設定(CSVの列名・シート名・セル書式)
CSV_FIELDS = ["商品コード", "商品名", "カテゴリ", "単価", "在庫数", "発注点"]
SHEET_ITEMS = "在庫一覧"
SHEET_AMOUNT = "在庫金額"
SHEET_REORDER = "補充対象"

TITLE_FONT = Font(bold=True, size=14)
HEADER_FONT = Font(bold=True, color="FFFFFF")
HEADER_FILL = PatternFill("solid", fgColor="1F4E78")
ALERT_FILL = PatternFill("solid", fgColor="FCE4E4")
TOTAL_FONT = Font(bold=True)
THIN = Side(style="thin", color="BFBFBF")
CELL_BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
TOTAL_BORDER = Border(left=THIN, right=THIN, top=Side(style="double"), bottom=THIN)
YEN_FORMAT = "#,##0"
INT_FORMAT = "#,##0"
RATE_FORMAT = "0.0%"

app = typer.Typer(add_completion=False, help="CSVの在庫データからExcel在庫報告書を作るCLIツール")

# サンプルCSVの元ネタ
SAMPLE_PRODUCTS = [
    ("文具", "油性ボールペン 0.7mm"),
    ("文具", "リングノート A5"),
    ("文具", "蛍光マーカー 5色セット"),
    ("文具", "クリアファイル A4"),
    ("事務用品", "コピー用紙 A4 500枚"),
    ("事務用品", "封筒 長形3号"),
    ("事務用品", "レターケース 5段"),
    ("事務用品", "デスクマット"),
    ("PC周辺", "USBメモリ 32GB"),
    ("PC周辺", "無線マウス"),
    ("PC周辺", "HDMIケーブル 2m"),
    ("PC周辺", "ノートPCスタンド"),
    ("備品", "折りたたみ椅子"),
    ("備品", "スチールラック"),
    ("備品", "ホワイトボード 900x600"),
    ("備品", "電源タップ 6口"),
    ("消耗品", "乾電池 単3形 20本"),
    ("消耗品", "アルコール除菌シート"),
    ("消耗品", "ゴミ袋 45L 100枚"),
    ("消耗品", "インクカートリッジ 黒"),
]


def _build_sample_rows(rows: int, seed: int) -> list:
    """乱数シードを固定してサンプルの在庫明細を組み立てる。"""
    rng = random.Random(seed)
    records = []
    for index in range(rows):
        category, name = SAMPLE_PRODUCTS[index % len(SAMPLE_PRODUCTS)]
        lap = index // len(SAMPLE_PRODUCTS)
        display_name = name if lap == 0 else "{}(型番{})".format(name, lap + 1)
        price = rng.choice([98, 128, 180, 240, 380, 520, 780, 1280, 1980, 3480])
        reorder_point = rng.choice([5, 10, 15, 20, 30])
        stock = rng.randint(0, reorder_point * 3)
        records.append(
            {
                "商品コード": "SKU-{}".format(1001 + index),
                "商品名": display_name,
                "カテゴリ": category,
                "単価": price,
                "在庫数": stock,
                "発注点": reorder_point,
            }
        )
    return records


# CSVの読み込みと在庫の計算
def _load_items(csv_path: Path) -> list:
    items = []
    with csv_path.open("r", encoding="utf-8-sig", newline="") as fp:
        reader = csv.DictReader(fp)
        missing = [name for name in CSV_FIELDS if name not in (reader.fieldnames or [])]
        if missing:
            raise typer.BadParameter("CSVに必要な列がありません: " + ", ".join(missing))
        for line_no, row in enumerate(reader, start=2):
            try:
                price = int(str(row["単価"]).strip())
                stock = int(str(row["在庫数"]).strip())
                point = int(str(row["発注点"]).strip())
            except (TypeError, ValueError):
                raise typer.BadParameter("{}行目の数値列を解釈できません".format(line_no))
            need_order = stock <= point
            items.append(
                {
                    "商品コード": str(row["商品コード"]).strip(),
                    "商品名": str(row["商品名"]).strip(),
                    "カテゴリ": str(row["カテゴリ"]).strip(),
                    "単価": price,
                    "在庫数": stock,
                    "発注点": point,
                    "在庫金額": price * stock,
                    "不足数": max(point - stock, 0),
                    "補充推奨数": max(point * 2 - stock, 0) if need_order else 0,
                    "判定": "要補充" if need_order else "適正",
                }
            )
    if not items:
        raise typer.BadParameter("CSVに明細行がありません")
    return items


# カテゴリ別の集計
def _summarize_by_category(items: list) -> tuple:
    totals = {}
    for item in items:
        bucket = totals.setdefault(item["カテゴリ"], {"品目数": 0, "在庫数": 0, "在庫金額": 0})
        bucket["品目数"] += 1
        bucket["在庫数"] += item["在庫数"]
        bucket["在庫金額"] += item["在庫金額"]
    total_amount = sum(bucket["在庫金額"] for bucket in totals.values())
    summary = []
    for category, bucket in sorted(totals.items(), key=lambda kv: kv[1]["在庫金額"], reverse=True):
        share = bucket["在庫金額"] / total_amount if total_amount else 0.0
        summary.append(
            {
                "カテゴリ": category,
                "品目数": bucket["品目数"],
                "在庫数": bucket["在庫数"],
                "在庫金額": bucket["在庫金額"],
                "構成比": share,
            }
        )
    return summary, total_amount


# openpyxlの共通スタイル適用
def _write_sheet_head(ws, title: str, headers: list, widths: list) -> None:
    ws.cell(row=1, column=1, value=title).font = TITLE_FONT
    for col, (header, width) in enumerate(zip(headers, widths), start=1):
        cell = ws.cell(row=2, column=col, value=header)
        cell.font = HEADER_FONT
        cell.fill = HEADER_FILL
        cell.alignment = Alignment(horizontal="center", vertical="center")
        cell.border = CELL_BORDER
        ws.column_dimensions[get_column_letter(col)].width = width
    ws.freeze_panes = "A3"


def _write_data_row(ws, row_index: int, values: list, formats: dict, fill=None) -> None:
    for col, value in enumerate(values, start=1):
        cell = ws.cell(row=row_index, column=col, value=value)
        cell.border = CELL_BORDER
        if col in formats:
            cell.number_format = formats[col]
        if fill is not None:
            cell.fill = fill


# シート1: 在庫一覧
def _build_items_sheet(ws, items: list) -> None:
    headers = ["商品コード", "商品名", "カテゴリ", "単価", "在庫数", "発注点", "在庫金額", "判定"]
    _write_sheet_head(ws, "在庫一覧(全明細)", headers, [12, 28, 12, 10, 10, 10, 14, 10])
    formats = {4: YEN_FORMAT, 5: INT_FORMAT, 6: INT_FORMAT, 7: YEN_FORMAT}
    row_index = 3
    for item in items:
        values = [item[name] for name in headers]
        fill = ALERT_FILL if item["判定"] == "要補充" else None
        _write_data_row(ws, row_index, values, formats, fill)
        row_index += 1
    ws.auto_filter.ref = "A2:{}{}".format(get_column_letter(len(headers)), row_index - 1)


# シート2: 在庫金額
def _build_amount_sheet(ws, summary: list, items: list, total_amount: int) -> None:
    headers = ["カテゴリ", "品目数", "在庫数合計", "在庫金額", "構成比"]
    _write_sheet_head(ws, "カテゴリ別の在庫金額", headers, [16, 10, 12, 16, 10])
    formats = {2: INT_FORMAT, 3: INT_FORMAT, 4: YEN_FORMAT, 5: RATE_FORMAT}
    row_index = 3
    for row in summary:
        values = [row["カテゴリ"], row["品目数"], row["在庫数"], row["在庫金額"], row["構成比"]]
        _write_data_row(ws, row_index, values, formats)
        row_index += 1
    total_stock = sum(item["在庫数"] for item in items)
    total_values = ["合計", len(items), total_stock, total_amount, 1.0 if total_amount else 0.0]
    _write_data_row(ws, row_index, total_values, formats)
    for col in range(1, len(headers) + 1):
        cell = ws.cell(row=row_index, column=col)
        cell.font = TOTAL_FONT
        cell.border = TOTAL_BORDER


# シート3: 補充対象
def _build_reorder_sheet(ws, items: list) -> tuple:
    headers = ["商品コード", "商品名", "カテゴリ", "在庫数", "発注点", "不足数", "補充推奨数", "発注予定金額"]
    _write_sheet_head(ws, "補充対象(在庫数が発注点以下)", headers, [12, 28, 12, 10, 10, 10, 12, 14])
    formats = {4: INT_FORMAT, 5: INT_FORMAT, 6: INT_FORMAT, 7: INT_FORMAT, 8: YEN_FORMAT}
    targets = sorted(
        [item for item in items if item["判定"] == "要補充"],
        key=lambda item: (item["在庫数"] - item["発注点"], -item["単価"]),
    )
    row_index = 3
    order_amount = 0
    order_quantity = 0
    for item in targets:
        amount = item["補充推奨数"] * item["単価"]
        order_amount += amount
        order_quantity += item["補充推奨数"]
        values = [
            item["商品コード"],
            item["商品名"],
            item["カテゴリ"],
            item["在庫数"],
            item["発注点"],
            item["不足数"],
            item["補充推奨数"],
            amount,
        ]
        fill = ALERT_FILL if item["在庫数"] == 0 else None
        _write_data_row(ws, row_index, values, formats, fill)
        row_index += 1
    if not targets:
        ws.cell(row=3, column=1, value="補充対象はありません").font = TOTAL_FONT
        return 0, 0
    total_values = ["合計", "", "", "", "", "", order_quantity, order_amount]
    _write_data_row(ws, row_index, total_values, formats)
    for col in range(1, len(headers) + 1):
        cell = ws.cell(row=row_index, column=col)
        cell.font = TOTAL_FONT
        cell.border = TOTAL_BORDER
    return len(targets), order_amount


# 帳票の読み取り用ヘルパー
def _read_rows(ws, max_rows: int) -> list:
    rows = []
    for row in ws.iter_rows(values_only=True):
        rows.append(row)
        if len(rows) >= max_rows:
            break
    return rows


def _format_cell(value) -> str:
    if value is None:
        return ""
    if isinstance(value, float):
        return "{:.1f}%".format(value * 100)
    if isinstance(value, int):
        return "{:,}".format(value)
    return str(value)


# コマンド1: サンプルCSVの作成
@app.command("sample-csv")
def sample_csv(
    output: Path = typer.Option(Path("inventory.csv"), "--output", "-o", help="出力するCSVのパス"),
    rows: int = typer.Option(24, "--rows", "-n", min=1, max=200, help="生成する明細行数"),
    seed: int = typer.Option(2026, "--seed", help="乱数シード(同じ値なら同じデータ)"),
) -> None:
    """動作確認用のサンプル在庫CSVを作成する。"""
    records = _build_sample_rows(rows, seed)
    output.parent.mkdir(parents=True, exist_ok=True)
    with output.open("w", encoding="utf-8-sig", newline="") as fp:
        writer = csv.DictWriter(fp, fieldnames=CSV_FIELDS)
        writer.writeheader()
        writer.writerows(records)
    low = sum(1 for record in records if record["在庫数"] <= record["発注点"])
    categories = sorted({record["カテゴリ"] for record in records})
    typer.echo("サンプルCSVを作成しました: {}".format(output.resolve()))
    typer.echo("明細行数: {}件 / カテゴリ: {}".format(len(records), ", ".join(categories)))
    typer.echo("うち在庫数が発注点以下の行: {}件".format(low))
    typer.echo("先頭3行のプレビュー:")
    typer.echo("  " + " | ".join(CSV_FIELDS))
    for record in records[:3]:
        typer.echo("  " + " | ".join(str(record[name]) for name in CSV_FIELDS))


# コマンド2: Excel帳票の生成
@app.command("build")
def build(
    csv_path: Path = typer.Argument(..., exists=True, dir_okay=False, readable=True, help="在庫CSVのパス"),
    output: Path = typer.Option(Path("inventory_report.xlsx"), "--output", "-o", help="出力する帳票のパス"),
) -> None:
    """在庫CSVから在庫一覧・在庫金額・補充対象の3シートを持つExcel帳票を生成する。"""
    items = _load_items(csv_path)
    summary, total_amount = _summarize_by_category(items)

    book = Workbook()
    ws_items = book.active
    ws_items.title = SHEET_ITEMS
    _build_items_sheet(ws_items, items)
    _build_amount_sheet(book.create_sheet(SHEET_AMOUNT), summary, items, total_amount)
    reorder_count, order_amount = _build_reorder_sheet(book.create_sheet(SHEET_REORDER), items)

    output.parent.mkdir(parents=True, exist_ok=True)
    book.save(output)

    total_stock = sum(item["在庫数"] for item in items)
    typer.echo("Excel帳票を作成しました: {}".format(output.resolve()))
    typer.echo("入力CSV: {} / 明細件数: {}件".format(csv_path, len(items)))
    typer.echo("在庫数合計: {:,}点 / 在庫金額合計: {:,}円".format(total_stock, total_amount))
    typer.echo("補充対象: {}件 / 発注予定金額: {:,}円".format(reorder_count, order_amount))
    typer.echo("シート: " + " | ".join(book.sheetnames))


# コマンド3: 生成済み帳票の内容確認
@app.command("inspect")
def inspect_report(
    book_path: Path = typer.Argument(..., exists=True, dir_okay=False, readable=True, help="確認する帳票のパス"),
    sheet: Optional[str] = typer.Option(None, "--sheet", "-s", help="内容を表示するシート名"),
    limit: int = typer.Option(8, "--limit", "-l", min=1, max=100, help="表示する明細の最大行数"),
) -> None:
    """生成済みのExcel帳票を読み取り、シート構成や明細を表示する。"""
    book = load_workbook(book_path, data_only=True, read_only=True)
    try:
        if sheet is not None and sheet not in book.sheetnames:
            typer.echo("シート「{}」はありません(存在: {})".format(sheet, ", ".join(book.sheetnames)))
            raise typer.Exit(code=1)
        typer.echo("帳票: {}".format(book_path.resolve()))
        typer.echo("シート数: {} / {}".format(len(book.sheetnames), " | ".join(book.sheetnames)))
        targets = [sheet] if sheet else list(book.sheetnames)
        body_limit = limit if sheet else 0
        for name in targets:
            ws = book[name]
            rows = _read_rows(ws, 2 + body_limit)
            title_text = _format_cell(rows[0][0]) if rows else ""
            headers = [_format_cell(value) for value in rows[1]] if len(rows) > 1 else []
            typer.echo("")
            typer.echo("[{}] {} ({}行 x {}列)".format(name, title_text, ws.max_row or 0, ws.max_column or 0))
            typer.echo("  見出し: " + " | ".join(headers))
            for row in rows[2:]:
                typer.echo("  " + " | ".join(_format_cell(value) for value in row))
        if not sheet:
            typer.echo("")
            typer.echo("明細を見るには --sheet と --limit を指定してください")
    finally:
        book.close()


def main() -> None:
    app()


if __name__ == "__main__":
    main()

コード全文は上の折り畳みに入れてあるので、全部を上から読む必要はありません。ここでは特に重要な部分だけを抜き出して、何をしているのか順番に見ていきます。

csvとopenpyxlのインポート

import csv
import random
from pathlib import Path
from typing import Optional

import typer
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.utils import get_column_letter

CSVの読み書きは標準ライブラリのcsv、サンプル値づくりはrandomに任せています。openpyxlからは、新しいブックを作るWorkbook、既存ファイルを開くload_workbook、書式用のFontPatternFillをまとめて読み込みました。

CSV列名とシート名の定数化

CSV_FIELDS = ["商品コード", "商品名", "カテゴリ", "単価", "在庫数", "発注点"]
SHEET_ITEMS = "在庫一覧"
SHEET_AMOUNT = "在庫金額"
SHEET_REORDER = "補充対象"

CSVの見出しとシート名を定数にすると、生成・書き込み・確認の3コマンドで同じ名前を共有できます。列構成を変えるときもCSV_FIELDSだけ直せばよく、書き間違いによるずれを防げます。

FontとPatternFillで見出しを装飾

TITLE_FONT = Font(bold=True, size=14)
HEADER_FONT = Font(bold=True, color="FFFFFF")
HEADER_FILL = PatternFill("solid", fgColor="1F4E78")
ALERT_FILL = PatternFill("solid", fgColor="FCE4E4")

見出し行は白い太字を濃紺の背景に載せ、明細と一目で区別できるようにしています。ALERT_FILLは補充対象の行を薄い赤で塗るための色で、fgColorにはRGBの16進コードを渡します。

Borderと表示形式の共通設定

THIN = Side(style="thin", color="BFBFBF")
CELL_BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
TOTAL_BORDER = Border(left=THIN, right=THIN, top=Side(style="double"), bottom=THIN)
YEN_FORMAT = "#,##0"

細い罫線THINを四辺に回したCELL_BORDERを明細セルに使い、合計行だけ上辺が二重線のTOTAL_BORDERへ差し替えます。YEN_FORMATnumber_formatに渡す文字列で、金額を3桁区切りで表示させるための指定です。

typer.Typerでアプリを定義

app = typer.Typer(add_completion=False, help="CSVの在庫データからExcel在庫報告書を作るCLIツール")

typer.Typer()で作ったappに、あとから3つのコマンド関数を登録していきます。helpはツール全体の説明文で、--helpを付けて実行したときに表示されました。add_completion=Falseにすると、シェル補完用のオプションが増えません。

_build_sample_rowsで再現できるダミー在庫

    rng = random.Random(seed)
    records = []
    for index in range(rows):
        category, name = SAMPLE_PRODUCTS[index % len(SAMPLE_PRODUCTS)]
        lap = index // len(SAMPLE_PRODUCTS)
        display_name = name if lap == 0 else "{}(型番{})".format(name, lap + 1)

random.Random(seed)で専用の乱数生成器を作るため、同じシードなら毎回同じサンプルが手に入ります。商品リストは%で巡回させ、2周目以降は名前に型番を足して商品名の重複を避けました。

価格と在庫数の作り方

        price = rng.choice([98, 128, 180, 240, 380, 520, 780, 1280, 1980, 3480])
        reorder_point = rng.choice([5, 10, 15, 20, 30])
        stock = rng.randint(0, reorder_point * 3)

単価と発注点は用意した選択肢から選び、在庫数は0から発注点の3倍までの範囲で決めています。この作り方だと発注点を下回る商品が自然に混ざるので、補充対象シートの確認がしやすくなりました。


Python基礎・実践(Django)

企業・法人向けのPython研修では、基礎から応用まで体系的に学べます。

Python研修の詳細

DX社員研修

企業・法人向けのDX研修では、実務に繋がるリスキリングでITレベルを向上させます。

DX研修の詳細

Javaエンジニア育成研修

企業・法人向けのJavaエンジニア育成研修では、Javaの基礎から応用まで確実に習得できます。

Java研修の詳細

新卒・新入社員向け研修

企業・法人に新入社員・新卒社員に向けたプログラミング研修を提供しています。

新入社員研修の詳細

コードキャンプのIT研修を全て見る

在庫金額と補充対象を分ける3シート設計

1枚のシートに全部詰め込むと読みにくいため、用途ごとにシートを分けました。明細を見る在庫一覧、金額を見る在庫金額、すぐ動くべき商品だけを見る補充対象という分担です。

在庫金額はカテゴリ別に集計し、合計行に太字と二重罫線を付けています。補充対象では在庫数が発注点以下の商品だけを抜き出し、行の背景色で目立たせました。

セルには=B2*C2のような数式ではなく、Python側で計算した値をそのまま書いています。ポイントとしては、帳票を読みやすくする表示設定を短くまとめます。

このセクションの用語

freeze_panes
指定したセルより上と左をスクロールしても固定表示する設定です。見出し行を固定すると、長い明細でも列名を見失いません。
auto_filter
Excelのオートフィルタを付ける設定です。見出しに絞り込みボタンが表示され、カテゴリなどで抽出できます。
number_format
セルの表示形式を決める文字列です。#,##0なら3桁区切り、0.0%ならパーセント表示になります。
data_only
load_workbookのオプションで、数式そのものではなく保存時に記録された値を読み取ります。

3シートの役割分担

シート 載せる内容 主な書式
在庫一覧 商品コード・商品名・カテゴリ・単価・在庫数・発注点と、計算した在庫金額の明細 見出し行の固定、オートフィルタ、#,##0
在庫金額 カテゴリ別に集計した在庫金額と、最下部の合計行 合計行は太字と二重罫線、#,##00.0%
補充対象 在庫数が発注点以下になった商品だけの一覧 該当行を薄い赤で塗りつぶし

数式ではなく計算済みの値を書く

セルに数式を入れると、Excelで開いて再計算されるまで結果が確定しません。openpyxlは数式を計算しないので、確認コマンドから読み戻しても数式の文字列しか取れなくなります。

そこで在庫金額もカテゴリ別の合計もPython側で計算し、確定した数値をセルへ書き込みました。読み取りはload_workbook(data_only=True, read_only=True)で行っています。

参考:

©openpyxl公式ドキュメント

openpyxl never evaluates formula. If you need the value of a formula, you have to use data_only=True when loading the workbook.

POINT

見出し固定:freeze_panes

絞り込み:auto_filterを設定

金額表示:number_formatで3桁区切り

列幅:get_column_letterで指定

さらに詳しく学べるPython研修の詳細はこちら

PythonのopenpyxlとTyper利用時に起きやすいエラーと対処法

ここからは、同じ構成のツールを動かすときに一般に起こりやすいエラーを整理します。どれも原因さえ分かれば、すぐ直せるものばかりです。

特に多いのは、ライブラリを入れた環境と実行しているPythonがずれているケースと、出力先のxlsxをExcelで開いたままにしているケースでしょう。

このセクションの用語

ModuleNotFoundError
読み込もうとしたライブラリが見つからないときに出るPythonのエラーです。インストール先と実行環境がずれていると起こります。
cp932
WindowsのExcelがCSVを保存するときによく使う日本語の文字コードです。UTF-8として読むと文字化けやエラーの原因になります。
KeyError
辞書に存在しないキーを取り出そうとしたときのエラーです。CSVの見出しが想定と違う場合に起こりやすいものです。
エラー例 原因 対処
ModuleNotFoundError: No module named 'openpyxl' ライブラリが未インストール、または別の仮想環境で実行している 実行に使っているPythonと同じ環境へopenpyxltyperを入れ直す
FileNotFoundError: [Errno 2] No such file or directory: 'inventory.csv' buildに渡したCSVのパスが違う、そもそもCSVをまだ作っていない 先にsample-csvでCSVを作るか、実際のファイル名とパスを確認して指定し直す
PermissionError: [Errno 13] Permission denied: 'inventory_report.xlsx' 出力先のxlsxをExcelで開いたまま上書きしようとしている Excelでファイルを閉じてから再実行するか、--outputで別名を指定する
UnicodeDecodeError: 'utf-8' codec can't decode byte ... CSVがcp932など、読み込み側と違う文字コードで保存されている CSVをUTF-8で保存し直すか、読み込み時のencodingを実ファイルに合わせる
KeyError: '単価' CSVの見出しがCSV_FIELDSと一致していない 見出し行を商品コード・商品名・カテゴリ・単価・在庫数・発注点の並びにそろえる

在庫報告書作成CLIで注意したい点

最初に引っかかりやすいのはCSVの文字コードです。ExcelでそのままCSV保存するとcp932になりやすく、UTF-8前提で読むとUnicodeDecodeErrorが出ます。

読み込み側をencoding="utf-8-sig"にしておくと、BOM付きのUTF-8でも先頭に余計な文字が混ざりません。書き出したCSVをExcelで開いて文字化けするときも、まず文字コードを疑うと早いでしょう。

もう一つはxlsxの上書きです。出力先をExcelで開いたままにしていると保存に失敗するので、実行前に閉じる習慣を付けておくと安心できます。

ポイントとしては、CSVの文字コードとExcelファイルの扱いで気を付けたい箇所をまとめます。

POINT

文字コード:utf-8-sigで読む

上書き前:Excelを閉じる

値の確認:data_only=True

読み込み:read_only=Trueで開く

さらに詳しく学べるPython研修の詳細はこちら

在庫報告書作成CLIの動作確認

作ったツールを、実際に4回のコマンドで動かしました。結果はいずれも正常終了(終了コード0)で、サンプル作成から帳票生成、内容確認までひと通り通っています。

まずsample-csv--rows 24を付けてinventory.csvを作り、続けてbuildinventory_report.xlsxを生成しました。最後にinspectで中身を読み返し、書き込んだ値が取り出せることを確かめています。

inspect--sheet--limitで表示範囲を絞れます。4回目は補充対象シートだけを5行に限定して実行しました。

このセクションの用語

終了コード
コマンドが終わるときに返す数値です。0は正常終了を表し、それ以外の値は何らかのエラーを示します。

実際に流した4つのコマンドです。

  1. python inventory_report.py sample-csv --output inventory.csv --rows 24
  2. python inventory_report.py build inventory.csv --output inventory_report.xlsx
  3. python inventory_report.py inspect inventory_report.xlsx
  4. python inventory_report.py inspect inventory_report.xlsx --sheet 補充対象 --limit 5
python inventory_report.py sample-csv --output inventory.csv --rows 24の実行結果(終了コード0)
python inventory_report.py sample-csv --output inventory.csv --rows 24の実行結果(終了コード0)
python inventory_report.py build inventory.csv --output inventory_report.xlsxの実行結果(終了コード0)
python inventory_report.py build inventory.csv --output inventory_report.xlsxの実行結果(終了コード0)
python inventory_report.py inspect inventory_report.xlsxの実行結果(終了コード0)
python inventory_report.py inspect inventory_report.xlsxの実行結果(終了コード0)
python inventory_report.py inspect inventory_report.xlsx --sheet補充対象 --limit 5の実行結果(終了コード0)
python inventory_report.py inspect inventory_report.xlsx --sheet補充対象 --limit 5の実行結果(終了コード0)
さらに詳しく学べるPython研修の詳細はこちら

在庫Excel帳票の自動生成が役立つ場面

このツールは在庫に限らず、同じ列構成のCSVを用意できる業務なら流用できます。列名と集計ルールを差し替えるだけで、別の帳票づくりにも転用しやすい作りです。

特に効くのは、毎月同じ帳票を手作業で組み直している場面でしょう。CSVさえ揃えば、体裁を保ったまま何度でも作り直せます。

このセクションの用語

棚卸
実際の在庫数を数えて帳簿上の数量と突き合わせる作業です。差異の確認や在庫評価額の算出に使われます。
cron
LinuxやmacOSで、決まった時刻にコマンドを自動実行する仕組みです。Windowsではタスクスケジューラが同じ役割を担います。
使える場面 具体的な使い方
月次の棚卸報告 棚卸後の在庫CSVをbuildに渡し、在庫一覧と在庫金額のシートをそのまま会議資料として配る
発注業務の下準備 inspect --sheet 補充対象で発注点を下回った商品だけを確認し、その一覧を発注担当へ回す
経理向けの在庫評価額の共有 カテゴリ別の在庫金額合計を月ごとに保存し、前月分のxlsxと並べて増減をチェックする
複数拠点の在庫集計 拠点ごとのCSVを同じ列名にそろえ、拠点別にbuildを実行して出力ファイル名で切り分ける
日次バッチでの自動化 タスクスケジューラやcronからbuildを呼び出し、毎朝最新の帳票を共有フォルダへ置く
さらに詳しく学べるPython研修の詳細はこちら

在庫報告書作成CLI開発のまとめ

Typerで3つのコマンドを定義し、openpyxlで3シートの帳票を書き出すところまでを1ファイルにまとめました。実際に4回のコマンドを流し、いずれも終了コード0で完了しています。

作ってみて効いたのは、計算をPython側に寄せてセルには値だけを書く方針でした。おかげでinspectから結果をそのまま読み返せて、目視確認がぐっと楽になります。

見出しの装飾や罫線を定数にしておくと、デザイン変更が1行で済みます。帳票づくりは細かい調整の繰り返しなので、この下準備が後半の作業を助けてくれました。

次に手を入れるなら試したい拡張のアイデアです。

  • 複数拠点のCSVをまとめて読み込み、拠点列を追加したうえで1冊のブックに集約する
  • 前月のxlsxをload_workbookで読み込み、在庫金額の増減を比べるシートを追加する
  • 補充対象の判定を発注点の1.2倍以下のように可変にし、しきい値をオプションで渡せるようにする
  • カテゴリ別の在庫金額をopenpyxl.chartで棒グラフにし、帳票の先頭シートへ貼り付ける
さらに詳しく学べるPython研修の詳細はこちら

参考にした一次情報

  1. ^ openpyxl - A Python library to read/write Excel 2010 xlsx/xlsm files. https://openpyxl.readthedocs.io/en/stable/, (参照26-09-03).
  2. ^ openpyxl: Working with styles. https://openpyxl.readthedocs.io/en/stable/styles.html, (参照26-09-03).
  3. ^ Typer公式ドキュメント. https://typer.tiangolo.com/, (参照26-09-03).
  4. ^ Python標準ライブラリ: csv --- CSVファイルの読み書き. https://docs.python.org/ja/3/library/csv.html, (参照26-09-03).

※内容は執筆時点のものです。ライブラリやサイトの仕様は変わる可能性があるため、公式ドキュメントもあわせてご確認ください。

ブログに戻る

コメントを残す

コメントは公開前に承認される必要があることにご注意ください。

企業・法人向けのIT・プログラミング・生成AI研修を探す、比較する - IT・プログラミングを知って学べるコネクトメディア CodeCampが提供するDX人材育成が可能なプログラミングやITが学べる公開講座 - IT・プログラミングを知って学べるコネクトメディア コードキャンプが提供する無料で学べるプログラミングスクール講座 - IT・プログラミングを知って学べるコネクトメディア コードキャンプDX人材育成研修 - IT・プログラミングを知って学べるコネクトメディア 3.5日の研修で、年間1,600時間の削減効果が見込まれる。東京きらぼしフィナンシャルグループのDX人材育成事例 - IT・プログラミングを知って学べるコネクトメディア 配属3ヶ月で30%の生産性向上を実現するいよぎんコンピュータサービスの新人研修に迫る - IT・プログラミングを知って学べるコネクトメディア 金融業界の業務効率化を加速するニッセイアセットマネジメントの生成AI×GAS活用研修事例 - IT・プログラミングを知って学べるコネクトメディア 【製造業のDX人材育成事例】デジタル人材の即戦力化を実現する、日本ガイシ株式会社の異動者向オンボーディング研修 - ITやプログラミングを知って学べるコネクトメディア フューチャーアーキテクト株式会社が実現した新入社員向けIT研修プログラムでタスクフォース制度が主体的な学びと成長を生み出す - IT・プログラミングを知って学べるコネクトメディア コードキャンプDX人材育成研修 - IT・プログラミングを知って学べるコネクトメディア コードキャンプIT・プログラミング研修事例/【IT新入社員研修】オンラインとオフラインの最適バランスを実現したFutureOneの導入事例 - IT・プログラミングを知って学べるコネクトメディア コードキャンプIT・プログラミング研修事例/【新入社員研修】柔軟なハイブリッド型Java研修で実現した新卒20名の成長と成果|サークレイス株式会社 - ITやプログラミングを知って学べるコネクトメディア コードキャンプIT・プログラミング研修事例/現場により近いところにデジタルを根付かせるDX基礎講座研修|株式会社ブリヂストン - ITやプログラミングを知って学べるコネクトメディア コードキャンプIT・プログラミング研修事例/業務の効率化・DX推進に向けたIT人材育成への第一歩|株式会社カナエ - ITやプログラミングを知って学べるコネクトメディア 企業・法人向けのIT・プログラミング研修 - ITやプログラミングを知って学べるコネクトメディア

What's New

新着記事

対象者別で探す

子供(小学生・中学生・高校生)向け
プログラミング教室検索する

子供(小学生・中学生・高校生)がロボットやプログラミング言語を学ぶことができるオフラインからオンラインスクールを検索、比較することが可能です。

子供(小学生・中学生・高校生)
プログラミング教室検索する

ITやプログラムなどの
最新情報を検索する

日々、新しいITやプログラミング言語の情報が流れていきますが、特定の情報を時系列でニュースやコラムを確認することができます。

ITやプログラムなどの
最新情報を検索する