にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

このブログについて

文字・フォント・プログラム・技術・趣味などについて、Twitterでは書きづらい長い内容などをまとめるためのブログです。基本的には自分用のメモとして書いている部分が多いです。

リンク等

note https://note.com/nixeneko 残らなくてもいい記事
Pixiv https://pixiv.me/nixeneko
Tumblr https://nixeneko.tumblr.com/ 絵。Pixivと同じにしたいがサボリ気味
Pleroma @nixeneko@nixeneko.info Mastodonとかやってる人はフォローしてください
Twitter @nixeneko
GitHub https://github.com/nixeneko プログラムとか
Bookmeter https://bookmeter.com/users/9166 読書記録。たまに感想
D4DJグルミク oDgpcYR9
Amazon欲しい物リスト amazon.jp/registry/wishlist/1C43ZFBA4IL6Z

同人誌(無料公開)

https://nixeneko.hatenablog.com/entry/c88_russian_alphabethttps://nixeneko.hatenablog.com/entry/c90_greek_latin_cyrillichttp://nixeneko.hatenablog.com/entry/20170811_dentyu

Amazon Timestreamからboto3で一度に大量のデータを取得しようとしたが空データが返ってきた

問題の概要

AWSAmazon Timestreamというデータベースにセンサーデータを蓄積している。このデータをローカルにダウンロードして利用したい。
Pythonとboto3ライブラリを利用して、Timestreamのデータベースからそこそこ大量のデータを取得しようとしたところ、レスポンスのRowsが空だった。
データがある程度小さくなるクエリでやってみると、問題なくデータが取得できるのが確認できた。

原因

TimestreamQuety.Client.query()を呼び出す際に、MaxRowsキーワード引数を指定しないと、取得結果のサイズが1MB以上になる場合に、Rowsが空の状態でレスポンスが返ってくる。その代わり、NextTokenがレスポンスに含まれる。

Otherwise, the initial invocation of Query only returns a NextToken , which can then be used in subsequent calls to fetch the result set. To resume pagination, provide the NextToken value in the subsequent command.

TimestreamQuery — Boto3 Docs 1.21.37 documentation

要するに、取得するデータがでかいと1回で取得することができないようになっている。

対応

ページネーションする。

TimestreamQuety.Client.query()NextTokenを指定して呼び出すと、サイズが1MB未満になるような個数のデータ(Rows)が返される。
更にまだ未取得のデータがある場合には、次のデータに対応するNextTokenもレスポンスに含まれる。データをすべて取得してしまった場合には、レスポンスにはNextTokenが存在しないので、終了判定にも使える。


実際にはこういう手順になる。

  1. 取得したいデータが1MB以上になる場合、最初に呼び出すとNextTokenと空のRowsが得られる。
  2. このNextTokenを利用して1MB程度分のデータ(Rows)と次のデータに対応するNextTokenを得る。
  3. これをNextTokenがレスポンスに含まれなくなるまで続け、今までに返されたRowsをすべて合体させれば欲しいデータが得られる。

Python 3コード

Rowsだけ連結して返すみたいなPython 3コードを示す。

import json
import boto3
from botocore.config import Config

#リージョン名, ID, secret keyは省略
config = Config(region_name = '……') 
config.endpoint_discovery_enabled = True
timestream_query_client = boto3.client('timestream-query', 
        aws_access_key_id="……",
        aws_secret_access_key="……",
        config=config)

#クエリの例
QUERY = """SELECT "time", "measure_name", "measure_value::double" FROM "mydatabase"."mytable" AND time between '2021-01-01 00:00' and '2022-01-01 00:00' ORDER BY time ASC""" 

#データ取得用の関数
def getdata(next_token = None):
    if next_token: #next_tokenが指定された場合はNextTokenを指定してクエリ
        result = timestream_query_client.query(
            QueryString=QUERY,
            NextToken=next_token
        )
    else: #next_tokenなし=初回呼び出し
        result = timestream_query_client.query(
            QueryString=QUERY
        )
    
    ret_rows = result["Rows"] #list of data
    if "NextToken" in result: #次のページ(未取得データ)が存在する場合
        return ret_rows + getdata(result["NextToken"]) #再帰呼び出し
    else: #欲しいデータはすべて取得した場合
        return ret_rows 

rows = getdata()
print(len(rows))
print(rows[0])
問題点

途中経過を保存していないので、ダウンロード途中で失敗した際に、やり直すときには最初から取得しなおしになる。
かなりたくさんのデータを取ってくる必要があるのであれば、呼び出しの度に毎回(あるいは何回かに一度)データとNextTokenを保存するなどとして、レジュームができるようにした方がよさそう。

また、再帰呼び出ししてるけど末尾再帰になってないので再帰深くなると大変かもしれない。メモリも食うし。普通にループで処理した方が適してるかも。

Python 3のElementTreeでXMLを解析する

PythonのElementTreeを使ってXMLを解析するときに少し悩んだので、忘れないようにメモ。
もっと例を洗練させるべきだとは思うが、後回しにする。

インポートとファイルの読み込み

import xml.etree.ElementTree as ET
tree = ET.parse('country_data.xml')
root = tree.getroot()

一方、ET.fromstring()メソッドで作成する場合、返されるのはルート要素となる。

タグを取り出す

以下、elemは何らかのタグに対応する要素とする(上のrootなど)

再帰

.iter()メソッドは、深いものについても順番に見ていくイテレータを返す。

for target_tag in elem.iter("target"):
    #...

とか。

直下で最初にみつけたもの1つ

.find()メソッドは直下のもののみ、最初に一致した要素一つだけを返す。

target_tag = elem.find("target")

これは後述するXPath記法と組み合わせることで格段に便利になる。

マッチしない場合はNoneになる。ただし、

if target_tag: #この書き方では動かない
    #マッチした場合

みたいには書けない(マッチした場合でも実行されない)。なので、

if target_tag is not None:
    #マッチした場合

みたいに書いたら動いた。

直下でマッチするものすべて

.findall()メソッドは直下のもののみ、一致した要素をすべて含むリストを返す。

for target_tag in elem.find("target"):
    #...
添え字でのアクセス

内側のタグには添え字でもアクセスできる。直下で最初くるタグのそのまた直下の最初に来るタグだと次のように書ける。

elem[0][0]

XPath記法

これを使えると格段に解析効率が上がる

今の階層以下の(直下とは限らない)タグを検索

//という、子要素やその子孫の要素すべてを対象とする記法が使える。

target = elem.find(".//target")
階層構造を指定
<a><b><c>こんにちは</c></b></a>
element = ET.fromstring("<element><a><b><c>こんにちは</c></b></a></element>")
target_c = element.find("a/b/c")
属性を指定
target = elem.find("target[@attrib='value']")

属性が"'を含む場合、エスケープするなり引用符を変更するなどすれば動く。

element = ET.fromstring('''<root><target name="Do's and dont's">some text</target></root>''')
target = element.find('''target[@name="Do's and dont's"]''')
2つ以上の属性を指定
target = elem.find("target[@attrib1='value 1'][@attrib2='value 2']")

属性の取り出し

element = ET.fromstring('<greetings myvalue="hello world"/>')
element.get("myvalue") #"hello world"

テキスト取り出し

<a><b>いz<c></c></b><d></d></a>

みたいな構造があった場合、
a.textは「あ」になる

a = ET.fromstring("<a>あ<b>い<c>う</c></b><d>え</d>お</a>")
print(a.text) #"あ"

ここで、「あいうえお」が欲しければ、

print( "".join(a.itertext()) ) #あいうえお

のようにするとよい。.itertext()で現在の要素以下のテキストを順番に返すイテレータが得られるので、それを合体させている。

2022年電子帳簿保存法に対応した、通販の領収書PDF類の保存法を考える

注意: 素人が調べたことなので間違い等を含む可能性があります。専門家の意見を聞くなり自分で原文にあたるなりして判断してください。

改定された「電子帳簿保存法」が2022年1月1日から施行され、通販などで領収書等をWebサイトで出力するものについて、プリントアウトして保存しておくことは認められなくなった*1

…となるはずだったが、2023年12月31日までの取引は、経過措置として、電子保存できないことについて「所轄税務署長がやむを得ない事情があると認め」た場合にはプリントアウトで保管してもよいということになったらしい*2
やむを得ない事情があると認めた場合云々といっても、特に申請や手続きは必要ない*3ため、来年末まではプリントアウトして保存しておいても問題なさそうではある。


とはいえ、2年のうちに準備しておかなければならない。

会計ソフトやクラウドサービスを利用している場合は、電子領収書等のデータを保存するための仕組みが多分つくられているだろうから、それを利用するのが簡単だと思われる。

そういうのを使わない場合はどうするか。


凡例: 鍵括弧「~」は用語や引用を表す。

電子取引の電子保存の要件

「電子取引」とは、取引情報(注文書、契約書、送り状、領収書、見積書等の情報)をインターネットや電子メール、EDI取引等でやりとりすることを指すとのこと*4
通販のWebサイトで出力した領収書PDFは電子取引の取引情報に該当するわけなので、電子的に保存する必要がある。


電子データとして保存する場合、次のような方法が認められる*5。以下に引用する。

  • 1 電子メールに請求書等が添付された場合
    • ⑴ 請求書等が添付された電子メールそのもの(電子メール本文に取引情報が記載されたものを含みます。)をサーバ等(運用委託しているものを含みます。以下同じです。)自社システムに保存する。
    • ⑵ 添付された請求書等をサーバ等に保存する。
  • 2 発行者のウェブサイトで領収書等をダウンロードする場合
    • ⑴ PDF等をダウンロードできる場合
      • ① ウェブサイトに領収書等を保存する。
      • ② ウェブサイトから領収書等をダウンロードしてサーバ等に保存する。
    • ⑵ HTMLデータで表示される場合
      • ① ウェブサイト上に領収書を保存する。
      • ② ウェブサイト上に表示される領収書をスクリーンショットし、サーバ等に保存する。
      • ③ ウェブサイト上に表示されたHTMLデータを領収書の形式に変換(PDF等)し、サーバ等に保存する。
  • 3 第三者等が管理するクラウドサービスを利用し領収書等を授受する場合
    • クラウドサービスに領収書等を保存する。
    • クラウドサービスから領収書等をダウンロードして、サーバ等に保存する。
  • 4 従業員がスマートフォン等のアプリを利用して、経費を立て替えた場合
    • 従業員のスマートフォン等に表示される領収書データを電子メールにより送信させて、自社システムに保存する。
    • なお、この場合にはいわゆるスクリーンショットによる領収書の画像データでも構いません。

引用終わり。基本的にPDFか、PDFが難しければスクリーンショット画像として保存するようである。



「電子取引の取引情報に係る電磁的記録の保存等を行う場合」には、次の要件を満たす必要があるらしい*6

  • 「電子計算機処理システムの概要を記載した書類の備付け(自社開発のプログラムを使用する場合に限ります。)」
  • 「見読可能装置の備付け等」
  • 「検索機能の確保」
  • 改竄防止措置

このうち、「電子計算機処理システムの概要を記載した書類の備付け」はまあ自分でプログラム書かなければ関係なさそう。

「見読可能装置の備付け等」は、電子取引の取引情報にアクセスできるパソコンを用意しておいて、税務調査のときにそれを利用してすぐに見せることができるようにしておけばいいらしい*7

あとは「検索機能の確保」と改竄防止措置である。

検索機能の確保

検索機能の確保については次の要件を満たす必要があるとのこと*8

  1. 取引年月日(などの日付)・取引金額・取引先を検索条件にできる
  2. 日付と金額は範囲を指定して検索できる
  3. 2つ以上の項目をAND検索できる

これは、次のような方法で対応できる。

規則的なファイル名を付けることによる対応

「2022年(令和4年)10月31日に株式会社国税商事から受領した110,000円の請求書」の場合であれば、

20221031_㈱国税商事_110000

などと、日付、取引先名、金額を含む規則的なファイル名をつけておくとよいとのこと*9

このような規則的な名づけを行うことで、ファイラーを使って検索ができ、検索要件を満たすことができるようだ。*10

しかしこのままでは範囲検索はできない。範囲検索がなくても、税務職員の求めに応じて提出できるようにしておけば、検索要件を満たすとしてよいらしい*11

また、検索要件は年商1000万円以下の場合は免除されるっぽい*12ので、該当する場合はこの名前付け自体必須ではないようだ。ただし必要に応じて提出できるようにしておく必要はあるはずだが。

範囲検索がしたければ、Pythonスクリプトなどを書いて、範囲検索を含んだ検索機能を実装するという手はある。

Excelを使った場合

もう一つの手としてはExcelを使うことがある。
Excelファイルに、領収書等のファイル名、取引の発生した日付、取引先、金額をリストとして入力しておけば、Excelの機能を利用して範囲検索やAND検索が可能である*13

改竄防止措置

「電子的に受け取った請求書や領収書等」の「真実性を確保する観点から、以下のいずれかの条件を満たす必要があ」るとのこと*14。以下引用する。

  1. タイムスタンプが付与されたデータを受領
  2. 速やかに(又はその業務の処理に係る通常の期間を経過した後、速やかに)タイムスタンプを付与
  3. データの訂正削除を行った場合にその記録が残るシステム又は訂正削除ができないシステムを利用
  4. 訂正削除の防止に関する事務処理規程を策定、運用、備付け

引用終わり。


さて、改竄等を防ぐための措置が必要であるが、クラウド会計サービスなどを使うのでなければ個人で改竄防止システムを用意するのは難しい。Gitだって歴史を改変できるので。

これは4.に従って、「電子取引データの訂正及び削除の防止に関する事務処理規程」を備え付けることで(そしてそれに従って管理することで)いいらしい*15
規程のサンプルは次のページからダウンロードできる。

まとめ

以下のような感じでデータ保存を行えば問題ないと思われる。

  • 基本的にPDFで保存する。PDFが難しい場合はスクリーンショット画像で保存する
  • いつでもデータを見られるパソコンを用意しておく
  • 日付・取引先名・取引金額を含んだ、規則的なファイル名をつけ、検索しやすいように場所を決めて保存する
  • 「電子取引データの訂正及び削除の防止に関する事務処理規程」を備え付ける

私は専門家ではないので正しさの保証はできません。最終的には参考の欄に挙げたPDFを自分で読んだり、専門家の意見を聞いて判断してください。

*1:所得税源泉徴収に係る所得税を除きます。)及び法人税の保存義務者が取引情報(注文書、領収書等に通常記載される事項)を電磁的方式により授受する取引(電子取引)を行った場合には、その取引情報を電磁的記録により保存しなければならない」(『電子帳簿保存法一問一答 【電子取引関係】』問1)

*2:電子帳簿保存法一問一答 【電子取引関係】』問41-2~問41-3

*3:電子帳簿保存法一問一答 【電子取引関係】』問41-4

*4:電子帳簿保存法一問一答 【電子取引関係】』問2

*5:電子帳簿保存法一問一答 【電子取引関係】』問27

*6:電子帳簿保存法一問一答 【電子取引関係】』問11

*7:電子帳簿保存法一問一答 【電子取引関係】』問13

*8:電子帳簿保存法一問一答 【電子取引関係】』問31

*9:電子帳簿保存法一問一答 【電子取引関係】』問12

*10:電子帳簿保存法一問一答 【電子取引関係】』問33

*11:「当該電磁的記録について、税務職員による質問検査権に基づくダウンロードの求めに応じることができるようにしている場合には、この項目を組み合わせて条件を設定できる機能(及び範囲を指定して条件を設定できる機能)は不要となります」(『電子帳簿保存法一問一答 【電子取引関係】』問32)

*12:電子帳簿保存法一問一答 【電子取引関係】』問34

*13:電子帳簿保存法一問一答 【電子取引関係】』問33

*14:電子帳簿保存法一問一答 【電子取引関係】』問22

*15:電子帳簿保存法一問一答 【電子取引関係】』問22~24

自営業でも適格請求書を発行したい: 請求書簡易作成システムの構成

概要

登録番号さえ用意して追加すれば適格請求書を作成できる(と思われる)システムを、Microsoft Excel, Microsoft Word, Pythonを利用して構成した。
データ入力をExcelで行い、Pythonスクリプトで形を変換し、そのデータを元にWordで差し込み印刷を行うことにより請求書を得る仕組みを作った。

作成したファイルやスクリプトは次のURLに置いた:
GitHub - nixeneko/seikyusho_sample: Microsoft Word, ExcelとPythonを使って請求書を作るサンプル

はじめに

インボイス制度とかいうものが始まるらしい、なんかやらないといけないらしい。

制度に関する説明は次のサイトがわかりやすい。
www.itmedia.co.jp


曰く、2023年10月から「適格請求書」というのを発行しないと、(得意先における)消費税の控除関係で不利になるらしい。なので「適格請求書」を必要になったら発行できるように準備しておきたい。

「適格請求書」の要件

請求書に次の内容を含めないといけないらしい。

  1. 書類作成者の氏名又は名称
  2. 取引年月日
  3. 取引内容(軽減税率の対象品目である旨)
  4. 税率ごとに区分して合計した税込対価の額
  5. 書類の交付を受ける事業者の氏名又は名称
  6. 登録番号
  7. 適用税率及び税率ごとに区分した消費税額等

参照:


現行の「区分記載請求書」では、6.と7.が要らないということで、この分が増えることになる。なお、6.の登録番号の発行は申請が必要となっている。とりあえず、6.は2023年10月から必要になるので、それまでは、いつでも追加できるようにしておいて、登録番号の表記を入れれば適格請求書となる状態をつくりたい。

なお、この記事では登録番号の発行を受けるための申請については扱わないこととする。


使うソフト

さて、請求書を発行するといっても、そのために専用のソフトを新しく買ったりとかはしたくない。

Microsoft WordとExcelを使うと差し込み印刷ができるらしいので、Microsoft Officeを基本として使うことにする。事務処理でMicrosoft Officeは使っていることも多いと思うので。

差し込み印刷

Wordには、Excelなどのデータを読み込んで文面を差し替える機能が備わっていて、「差し込み印刷」という。

差し込み印刷については次のページなどが参考になる:
差し込み印刷で請求書のように複数レコードや合計を表示したい:Word(ワード)2013基本講座


以下、Microsoft OfficeのバージョンはMicrosoft 365 MSO (バージョン 2112 ビルド 16.0.14729.20156) 64 ビットを使っているが、基本的に別のバージョンでも問題ないと思われる。

差し込み印刷のフォーマットをつくる

Wordでひな形をつくる
f:id:nixeneko:20220107151249p:plain
請求書ひな形

こんな感じのを作った。

税込みベースの作業フローを使っていたのでそのまま税込みベースで計算しているが、統一されていれば税別ベースで計算してもよい*1

Excelで、差し込めるデータ構造を決める

サンプルデータを用意した。こんな感じのものであれば差し込み印刷で問題なく使える。シートの名前は「請求書」とした。

f:id:nixeneko:20220107232007p:plain
Excelサンプルデータ

必要なのはテキストや数値のみであって、幅やフォーマットは関係ない。
グレーの部分はデータとしては利用していないので、空欄や別の値でも問題ない。

Wordに差し込み印刷を設定する

[差し込み文書]タブを開く→[差し込み印刷の開始]→[標準のWord文書]をクリック

[宛先の選択]→[既存のリストを使用(E)...]をクリック

先ほどサンプルデータを用意したエクセルファイルを選んで、[開く(O)]を押す。

[テーブルの選択]ウィンドウが出るので、データのあるシート(ここでは「請求書$」)を選択して[OK]を押す。
([先頭行をタイトル行として使用する(R)]にチェックが入っていることを確かめる。)

___様となっているところにカーソルを移動して、[差し込みフィールドの挿入]→「被請求者」を選択すると«被請求者»と書かれたものが挿入される。

同様に年月日のところに「年」「月」「日」の差し込みフィールドを挿入する。


注文内容の部分には工夫が必要である。
1行目は次のように差し込みフィールドを挿入する。

  • 品目→「品目」
  • 税込単価→「単価」
  • 個数→「個数」
  • 小計→「品目小計」

ここで、挿入された«単価»を右クリックし、[フィールド コードの表示/非表示(T)]をクリック。
すると{ MERGEFIELD 単価 }というような表示になるので、閉じ曲括弧}の直前に\# ¥,0 を入力して次のようにする。

{ MERGEFIELD 単価 \# ¥,0 }

これを再度右クリックして[フィールド コードの表示/非表示(T)]をクリック。

追加したコードの意味は次のようになっている。

  • \# はnumeric picture switchというWordの機能らしく、数値の表示形態を指定する。
  • ¥は円記号(U+00A5)である。
    • 円記号¥(U+00A5)の代わりにバックスラッシュ\(U+005C)を用いる場合は、\\としなければならない。ただ、最新のコンピュータではバックスラッシュが円記号で表示されないことも多くなってきているし、円記号の方を使っておけばいいと思う。昔のシステムではわからないが…
  • ,は3桁ごとにコンマ区切りをすることを表す。
  • 0は整数で、1桁の場合でも先頭を0パディングしないことを表す。

参考: Fix the Formatting of an Excel Mail Merge Field in a Word Document


同様に«品目小計»のフォーマットも指定する。


2行目の品目の欄に移動し、[ルール]→[Next Record If (条件により次のレコード)(X)...]をクリック。

[Wordフィールドの挿入: Next Record If]ウィンドウが表示されるので、

  • [フィールド名(F)] →「請求書ID」
  • [比較(C)] → [空白でない]

にして[OK]をクリックすると«Next Record If»と挿入される。

その直後に「品目」という差し込みフィールドを挿入する。
2行目の他のセル、税込単価, 個数, 小計のところは1行目と同じでよい。

Next Record Ifというのがなぜ必要かというと、これがないと一つの請求書で1つの行しか参照せず、同じ品目が並ぶことになる。そのため、「次の行に進んでください」とWordに指示を出すコマンドが必要で、それがNext Record Ifである。ここでは「請求書ID」が空白でない場合のみ次の行に移るように設定したので、空白行より先には進まないでそこで止まる。


3行目以降は2行目と同じてよく、2行目をコピペすればよい。


また、他のものについても同様に差し込みフィールドを挿入し、フィールドコードを編集することで円記号前置+コンマ区切りを指定する。

  • ご請求金額→「代金計」{ MERGEFIELD 代金計 \# ,0 }
  • 合計金額→「代金計」{ MERGEFIELD 代金計 \# ¥,0 }
  • 税率10%対象金額(税込)→「標準税率対象」{ MERGEFIELD 標準税率対象 \# ¥,0 }
  • 10%税額→「標準税」{ MERGEFIELD 標準税 \# ¥,0 }
  • 税率8%対象金額(税込)→「軽減税率対象」{ MERGEFIELD 軽減税率対象 \# ¥,0 }
  • 8%税額→「軽減税」{ MERGEFIELD 軽減税 \# ¥,0 }
  • 消費税計→「消費税計」{ MERGEFIELD 消費税計 \# ¥,0 }


出来上がったものは次のような見た目になる。

f:id:nixeneko:20220107155810p:plain
差し込みフィールドの挿入が済んだ請求書テンプレート


ここで、[差し込み文書]タブの[結果をプレビュー]をクリックしてオンにすると最初のデータがプレビューされる。

実際の差し込みでは、[差し込み文書]タブの[完了と差し込み]から[個々のドキュメントの編集(E)]でWordファイルを作成するか、[文書の印刷(P)]で印刷することになると思う。

実際に差し込みを行ったものが次図である。

f:id:nixeneko:20220110160329p:plain
差し込みを行った請求書


制限としては、この請求書の書式では注文内容の明細を示すための欄を16行しか確保していないので、16種類以上の品目が出てきた場合に望む結果が得るられないことになる。
とはいえそんなたくさんの品目を扱うならもっとちゃんとしたシステム導入するでしょという気もする。

入力を簡単にしたい

さて、差し込みは出来上がったものの、差し込み用のExcelデータを手入力するのはすごく面倒である。
そのため、必要最低限の情報を入力するExcelファイルを作り、適宜変換することにする。Excelは入力には便利なので。
もちろん注文内容をデータで持っているのであればそれを変換してやればそれで済むのだが、紙ベースで管理しているので入力するしかない。


変換は、本当であればすべてExcelでやりたかったのだが、やり方がよくわからなかったのでPythonで変換することとした。Pythonを選んだのは単に使い慣れてるからで、同じことができれば何でもよい。

f:id:nixeneko:20220110144725p:plain
請求書作成システム全体図

入力用Excelファイル → 変換 → 出力用Excelファイル → Word差し込み → 請求書Wordファイル ……という流れになる。

要件を考える

さて、Pythonで変換するにあたって、
出力にあたって必要なのは

  • 請求先名称
  • 請求年月日
  • 請求金額(合計金額)
  • 標準税率対象金額
  • 標準税額
  • 軽減税率対象金額
  • 軽減税額
  • 消費税計
  • 品目のリスト

であり、品目についてはさらに

  • 品名(軽減税率対象の場合は最後に「(※)」とつける)
  • 税込単価(税別でもいいが全体で統一する)
  • 個数
  • 小計

をもつ必要がある。

また、品目については、送料を最後にまとめて表示するようにしたい。


税率の計算では小数点以下を扱う必要が生じるが、これはdecimalを利用する。誤差があると困るのでfloatは使わない方がいい。


消費税の計算については、

  1. 標準税率対象金額から標準税額を計算し、整数に丸める
  2. 軽減税率対象金額から軽減税率を計算し、整数に丸める
  3. 標準税額と軽減税額を合計する

という順番(ただし1.と2.は順不同)で、税率ごとの対象金額の合計に対して整数に丸め、合計することになっている。
整数への丸め方(端数処理)は、切り捨て、切り上げ、四捨五入などのどれでもよいとのこと。とりあえず四捨五入にしておく…。
参考: 切り捨て?切り上げ?請求書の端数処理について解説 | 経営者から担当者にまで役立つバックオフィス基礎知識 | クラウド会計ソフト freee

入力用Excelファイルの構造を考える

入力用のExcelファイルは次のようなものを作った。

f:id:nixeneko:20220110145450p:plain
入力用Excelファイル

以下概略である。

  • 「請求書ID」が同一のものが1つの請求書となる。
  • 「被請求者」が請求先として記載される。
  • 「商品品目」はドロップダウンリストからの選択式とした。
    • ドロップダウンリストの定義は「品目リスト」シートのテーブルで行っている。
    • 自由入力も可能としている。
  • 「軽減」は軽減税率対象の場合はxを入力する。
    • 「商品品目」をドロップダウンリストから選択した場合は自動入力されるようにした。
  • 「単価」はドロップダウンリストから選択した場合は自動入力されるようにした。
  • 「送り先〒」に郵便番号を入力するか、「送り先住所」に都道府県を入力する。
    • 「送り先〒」は文字列で、入力すると隣の「送り先住所」に都道府県が入るようにしている。
      • 住所を都道府県から書かない人がいるので、これがないと調べる手間が増える。
    • 郵便番号から都道府県を引くのは「郵便番号表」 シートのテーブルで行っている。
  • 「サイズ」は荷物のサイズで、商品品目がリストにあれば自動入力される他、プルダウンで変更できる。
    • 大きな箱単位のものを想定しているので自動入力を用意しているが、発送する荷物の数と一対一対応しないことがほとんどの場合は自動入力をなくした方がよさそう。
  • 「送料単価」は「送り先住所」と「サイズ」から自動入力される。
    • 都道府県送料」シートから引いている。
  • 「送料個数」は「個数」と同一の数値が自動で入力されるようにしているが、任意で変更できる。
    • 必要がなければ空欄にするか0にする。
    • 商品の個数と一対一対応することがまれな場合は自動入力をなくした方がよさそう。
プルダウンリストの元の値にテーブルを利用する

プルダウンリストの[元の値]にテーブルを利用するには、INDIRECT()関数を使う。
連動するドロップダウンリストをテーブルを利用して作成する:Excelの基本操作

ドロップダウンリストと自由入力を混在させる

データの入力規則として、ドロップダウンリストと自由入力を混在させるためには、[データの入力規則]の[エラーメッセージ]タブの設定を変更する。
Excel:入力規則でドロップダウンリストと入力を混在させたい  −教えて!HELPDESK

郵便番号表の作成
  1. [データ]タブ → [データの取得] → [ファイルから(F)] → [テキストまたはCSVから(T)] → KEN_ALL.CSVを選択、[インポート]を押す。
  2. ウィンドウが開くので、[データ型検出]を[データ型を検出しない]に変更して[読み込み]を押す。
  3. 読み込まれるので、不要な列を消す。
  4. 全体を選択して、[セルの書式設定]→[表示形式]を[文字列]にして[OK]を押す。
  5. シート名を郵便番号表にする(しなくてもいい)。
  6. [テーブル デザイン]タブから[テーブル名]を郵便番号テーブルにする

変換スクリプトの用意

seikyusho_sample/convert.py at main · nixeneko/seikyusho_sample · GitHub
変換用のPythonスクリプトを用意した。

実行に必要なのは

である。

使い方

端末やコマンドプロンプト*2を開いて、

python convert.py 請求データ.xlsx --date 2022-01-06

などとすると、processed.xlsxというファイルが出力される。
コマンドラインオプションとして、--dateで日付指定、--min_id, --max_idでIDの範囲指定ができるようにしてある。


一度差し込み印刷のファイルを設定してしまえば、同じファイルパスをであれば請求書テンプレートのWordファイルを開くときに自動で読み込まれるので、請求書出力プロセスは次のようになる。

  1. 入力用Excelファイルへの入力
  2. pythonスクリプトで変換
  3. 請求書テンプレートのWordファイルを開き、差し込みを実行する

というわけで、かなり楽になった。

サンプルのダウンロード

次のリンクからダウンロードできます。
github.com
なお、動作保証等はしませんので、利用される場合は自己責任で行ってください。

さいごに

請求書を手書きからコンピュータ作成に移行しようというところだったので、ついでに適格請求書に対応できるような仕組みを準備した。

取扱商品が少なく固定されている場合は商品ごとに列を用意して数だけ入力するとかにしてもよいし、自分のところで扱っている商品によってシステムを構成することでかなり楽ができる。

効率化していきましょう。

*1:というか、レシートとか見ると税別で計算してる方式の方が多いような気もする。ここら辺正直良くわかってないので調べないといけない…。

*2:手元ではCygwin上のpython3を使って動作確認をしている。

Wordファイル公開にあたって、個人情報メタデータを消す

Microsoft Word (.docx)ファイルには本文以外にも、作成者名やファイルパス等の個人情報がメタデータとして含まれている可能性がある。

公開前に消しときたいというのはある。

ついでにExcelやPower Pointなどの他のMicrosoft Officeファイルでも同様である。

方法A: Windowsエクスプローラーを使って作成者名等を消す

  1. WindowsエクスプローラーからWordファイルを右クリック→[プロパティ(R)]
  2. [詳細]タブ→[プロパティや個人情報を削除]をクリック
  3. 必要な設定を変更するなどして、[OK]を押す

参考: Word , Excel , PowerPoint のタイトルや作成者などプロパティ情報を変更または削除する方法。 | PC&IT ~i-TSUNAGU~

方法B: Wordから削除・編集する

Wordのバージョンは「Microsoft® Word for Microsoft 365 MSO (バージョン 2112 ビルド 16.0.14729.20156) 64 ビット」である。

[ファイル]→[情報]を開くと、右側に[関連ユーザー]のところに[作成者]と[最終更新者]が表示されている。

[作成者]についてはこのユーザーを右クリックして削除や変更ができるが、[最終更新者]については変更ができない用に見える。

これらを消すためには、

  1. [ファイル]→[情報]→[問題のチェック]([文書の検査]の左)をクリック→[ドキュメント検査(I)]をクリック
  2. [ドキュメントのプロパティと個人情報(U)]にチェックが入っていることを確認し、[検査(I)]をクリック
  3. [ドキュメントのプロパティと個人情報]の右側に表示される[すべて削除]をクリック→[閉じる(C)]をクリックして閉じる
  4. ファイルを保存する

このような手順でできる。

参考: Wordのファイルで、自分ではない作成者名が表示されてしまうのですが? | 日経クロステック(xTECH)

方法C: .docxファイルを直接いじる

.docxファイルがXMLファイル等を.zip圧縮したものであるというのは周知の通りである。

なので、展開して内部のXMLファイルを変更し、再度圧縮して.docxという拡張子に戻すことで変更できる。
テキストファイルなので全文検索を通しておくと安心だと思う。


今回自分が編集していたWordファイルでは、

  • docProps/core.xmldc:creator, cp:lastModifiedByタグに作者の名前が出るっぽかったので、書き換えると良さそう。

あと、差し込み文書用データのExcelファイルのパスが

  • word/_rels/settings.xml.rels
  • word/settings.xml

に含まれていたので、ファイルの場所を個人情報を含まないものに変更しておくなどするとよいかもしれない。

Online Armenian Storeで買物しようとしたら日本へは送れないよと言われた

アルメニア語の辞書が欲しくて探していたところ、Online Armenian Storeというアルメニアの物品を売っているサイトを見つけた。
onlinearmenianstore.com
商品名が英語で書かれているので、検索も楽だし、アルメニア語わからなくてもだいたいどんな本かわかるので便利そうだ。

しかし、ここで買おうとしたら、お前のアドレスには配送できないよというエラーが表示された。

This order can’t be shipped to the address you entered. Review your address to ensure that all fields have been entered correctly and try again.
配送方法がない

さて、問題の商品を見てみると、Shipped from USと書いてある。

商品ページにShipped from USと書いてある
Shipped from USと書いてある商品

一方で、検索してみると、Shipped from Armeniaと書かれている商品もある。

商品ページにShipped from Armeniaと書いてある
Shipped from Armeniaと書いてある商品

ここで、サイトの最下部に

Worldwide Shipping
We ship from Armenia using Haypost and Onex. ...

と書いてあることを考えると、どうやらアメリカから発送するのは日本まで送ってくれなさそうだ。

というわけで、カートからShipped from USと書かれた商品をすべて削除したら送料選択の画面に進むことができた。送料も安いし便利そうだ。

配送

アルメニアから国際書留で送られてきた。注文から届くまで3週間ちょっとかかった。2週間以内では確実に届かないだろうので、気長に待つのがよいかもしれない。

f:id:nixeneko:20220102174241j:plain
届いた辞書

Windows用入力言語切り替えボタンを作る

概要

youtu.be

キーパッドAutoHotkeyスクリプトを組み合わせることでキーボードレイアウトを切り替える仕組みを作った。
F13-F24のキーを使用することで、キーボードの既存のキーを犠牲にすることなく実現ができた。

あいさつ

この記事は「語学・言語学・言語創作 Advent Calendar 2021」の13日目として書かれています。

語学・多言語学習畑から乱入失礼します。なんかconlang系の人が多そうですが、混乱させてしまったらすみません。


……。

はじめに

さて、多言語をかじったりしていると、いろいろな言語を入力する必要があります。
Windowsで多言語入力をするには、キーボードレイアウトを言語ごとに追加し、切り替えて入力することが多いと思います。(以下ではキーボードレイアウトは単に「キーボード」と書き、物理的なキーボードは「物理キーボード」と書いて区別することとします。)

この切り替えは言語ごとに順繰りに送っていく方式で、2~3個ならまだ切り替えるのは問題ないのですが、5個以上になってくると、目的のキーボードまで切り替えるのがかなり面倒になります。別の言語に切り替えるためにAlt+Shiftを5連打とかしないといけないのはあんまりうれしくないわけです。
ラテン文字系であれば、WinComposeなどの汎言語的に入力できるツールを使えばキーボードを切り替えなくてもよいものの、非ラテン文字の場合は使うことはできません。

そのため、キーボードを、つまり実質的には入力言語*1を切り替えることができる専用のボタンがあれば便利なのではないでしょうか。

前に検討した仕組みの問題点

私は以前にAutoHotkeyを利用してキーボードを切り替える仕組みを検討していました。

しかし、これは物理キーボードのキーを横取りしているため、使用したキーが入力できなくなってしまうという副作用が生じます。

これについては、「Windowsが認識できるが、普通のキーボードには存在しないキーコード」を出力する物理キーボード(キーパッド)を用意すれば解決します。具体的にはF13~F24というキーコードが存在していて、Windowsが認識できるようになっているので、これを使うことにしました。

キーパッド: meishi2

f:id:nixeneko:20211212200801p:plain
自分で適当なキーコードを設定できるキーパッドを用意します。キーパッドはマクロパッドとも呼ばれ、要するに「自由にキーを設定できる、キーの少ないキーボード」のことです。今回はmeishi2というキーボードキットを使いました。

キーの割当ですが、左からF13, F14, F15, F16としました。また、4言語では足りないのでは?と思い、2キーの同時押しに対してもF17~F22を割り当てています。


キーの割り当てを設定するために、QMK Firmwareをビルドして書き込むみました。

QMK Firmwareのビルドについては特に解説しませんが、次のページを参考にしています。

qmk_firmware\keyboards\meishi2\keymaps\lang_selector以下に次のキーマップのファイルをコピーし、

make meishi2:default:avrdudeとかするといいです。

AutoHotkeyスクリプト

github.com
作成したAutoHotkeyスクリプトをここに置きました。

使用準備

  1. F13-F24のキーをもつキーパッドを繋ぎます。
  2. AutoHotkey v1.1をインストールします。
  3. change_settings.ahkを起動します。
  4. 現在のキーボードを、キーに割り付けたいキーボードに変更します。
  5. キーパッドF13-F24のいずれかのキーを押します。するとそのキーに現在のキーボードが割り付けられ、その旨を示すダイアログが表示されるのでOKを押して閉じます。settings.iniが更新されます。
  6. 他にキーボードがあれば 4. に戻ります。
  7. 通知領域のAutoHotkeyのアイコンを右クリックし、Exitを選んで終了します。

キーボードの切り替え

  1. keyboard_changer.ahkを起動します。
  2. F13-F24のキーを押すと、先に設定したキーボードに切り替わります。

制限

現在は「キーボードの切り替え」であり、IMEの切り替えには対応していません。そのため、「Google日本語入力」と「Microsoft 日本語 IME」を切り替えるみたいなことができません。これはIMEはTSFという枠組みで動いているので、キーボード自体は同じものを指していて、キーボード切り替えの仕組みでは区別ができないためです。
これについてはTSF系のAPIを叩くことでなんとかできそうな気はします。

また、IMEのオン・オフを指定して切り替える仕組みも作ったのですが、利用したIME.ahkのライセンスが不明なので省いてあります。
詳しくはkeyboard_changer.ahkのソースを見てもらえばよいのですが、IME.ahkをダウンロードしてきてIME_SET関数をコピペし、関連するコメントアウトを外し、settings.iniを適切に設定すると動くようにはなります。

おわりに

多言語入力においてキーボードの切り替えが面倒だったために、Windowsに同時に設定するキーボードの数を3個程度に制限していたのですが、これで遠慮なく増やせます。現在の状態を意識しなくても目的の言語に切り替えられるのは便利です。

みなさんも素敵な多言語入力ライフをお過ごしください。

.i mi do ckire lo nu tcidu kei .i co'o rodo
.i lo lenku tcima cu ba ranji .i ko ko kurji

2021-12-13 15:45追記

こんだけ書いといてなんですが、Windowsで入力言語に対してショートカットキーが設定できました。
以前ショートカットキー設定したときは設定が反映されなかったので検討しなかったのですが、動くようになっていたのですね。

Windowsの設定→「デバイス」→「入力」→「キーボードの詳細設定」→「入力言語のホットキー」
で出てくるウィンドウで目的のキーボードに対してキーシーケンスを設定すればいいようです。

設定したキーシーケンスを出力するキーボードを用意すればAutoHotkeyスクリプト要らなかったですね。
まあショートカットキーが設定できるキーシーケンスは限られているため、ショートカットキーが被る可能性はあり、ショートカットキーの競合が少なくなるという点でAutoHotkeyを使う優位性はあるかもしれません。

*1:一つの言語に対して複数のキーボードを使うことは少ないと思ってこう書いていますが、一言語を複数の用字系で表記する場合などに同じ言語の複数のキーボードを使う必要が生じたりします。