"""記事 invoice-tax-rounding-monthly の計算(商いの帳場)。python scripts/figs/invoice_tax_rounding.py で再現できる。
架空の1か月分の納品データ(top_numbers.py の 3 と同じ乱数の種・同じ作り方)で、消費税額を3通りに計算する。
  A 明細ごとに端数処理して合計(国税庁 Q&A 問57 の注で認められない方法。比べるためだけに計算)
  B 納品書ごとに税率ごとに1回(問67:納品書に税率ごとの消費税額等を書き、納品書と請求書で適格請求書にする場合)
  C 月まとめの請求書で税率ごとに1回(問65:請求書を適格請求書にする場合)
金額はすべて税抜、税率は8%と10%。端数処理は切り捨て・四捨五入・切り上げの3つで計算する。外部のデータは使わない。
出力:標準出力に JSON、public/img/articles/invoice-tax-rounding-monthly-{3ways,decide,lost}.svg(図1〜3)。
"""
import json, random, math, pathlib, sys
sys.stdout.reconfigure(encoding='utf-8')
from decimal import Decimal, ROUND_HALF_UP

DECIDE = """<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 720 360' width='720' height='360' role='img' aria-labelledby='itr-dec-t' font-family="'Hiragino Sans','Yu Gothic','Meiryo',sans-serif"><title id='itr-dec-t'>どの書類を適格請求書にするかで端数処理の単位が決まる判定図。納品書に税率ごとの税額を書くなら納品書ごとに税率ごとに1回(問67、架空の1か月で102,085円・37回)、書かないなら月まとめの請求書で税率ごとに1回(問65、102,097円・2回)。明細ごとの丸めはどちらでも不可(問57の注)</title><rect width='720' height='360' fill='#fff'/><rect x='150' y='20' width='420' height='58' rx='6' fill='#f1f1f1' stroke='#a8a8a8'/><text x='360' y='45' font-size='14' font-weight='700' fill='#111111' text-anchor='middle'>毎回の納品書に、税率ごとの対価の合計・</text><text x='360' y='66' font-size='14' font-weight='700' fill='#111111' text-anchor='middle'>税率・消費税額を書くか?</text><path d='M250 78 V112' stroke='#444444' stroke-width='1.6'/><path d='M244 104 L250 114 L256 104' fill='none' stroke='#444444' stroke-width='1.6'/><text x='262' y='100' font-size='13' font-weight='700' fill='#557323'>書く</text><path d='M470 78 V112' stroke='#444444' stroke-width='1.6'/><path d='M464 104 L470 114 L476 104' fill='none' stroke='#444444' stroke-width='1.6'/><text x='482' y='100' font-size='13' font-weight='700' fill='#557323'>書かない</text><rect x='30' y='118' width='320' height='150' rx='6' fill='#fff' stroke='#678b2c' stroke-width='2'/><rect x='30' y='118' width='320' height='34' rx='6' fill='#b5cc8e'/><text x='190' y='141' font-size='14' font-weight='700' fill='#111111' text-anchor='middle'>納品書ごとに税率ごとに1回(問67)</text><text x='46' y='176' font-size='12.5' fill='#444444'>納品書+請求書で適格請求書にする</text><text x='46' y='198' font-size='12.5' fill='#444444'>請求書に登録番号と納品書番号</text><text x='46' y='220' font-size='12.5' fill='#444444'>請求書の税額の合計は参考の扱い</text><text x='46' y='252' font-size='13.5' font-weight='700' fill='#557323'>架空の1か月:102,085円(37回)</text><rect x='370' y='118' width='320' height='150' rx='6' fill='#fff' stroke='#678b2c' stroke-width='2'/><rect x='370' y='118' width='320' height='34' rx='6' fill='#678b2c'/><text x='530' y='141' font-size='14' font-weight='700' fill='#ffffff' text-anchor='middle'>請求書で税率ごとに1回(問65)</text><text x='386' y='176' font-size='12.5' fill='#444444'>月まとめの請求書に税率ごとの税額</text><text x='386' y='198' font-size='12.5' fill='#444444'>取引の内容は請求書か納品書に</text><text x='386' y='220' font-size='12.5' fill='#444444'>納品書の様式は変えなくてよい</text><text x='386' y='252' font-size='13.5' font-weight='700' fill='#557323'>架空の1か月:102,097円(2回)</text><rect x='30' y='284' width='660' height='40' rx='6' fill='#fbeeea' stroke='#c23b22'/><text x='360' y='309' font-size='13.5' font-weight='700' fill='#a3301b' text-anchor='middle'>どちらでも不可:明細ごとに丸めた税額の合計(問57の注) 102,067円(90回)</text><text x='30' y='348' font-size='11' fill='#6b6b6b'>国税庁Q&amp;A(令和8年5月改訂)問57・65・67をもとに商いの帳場が作図。金額は架空のデータ(切り捨て)の計算値</text></svg>"""
ROOT = pathlib.Path(__file__).resolve().parents[2]
rng = random.Random(20261016)
deliveries = []
for i in range(20):
    lines = []
    for _ in range(rng.randint(2, 6)):
        rate = 8 if rng.random() < 0.7 else 10
        price = rng.choice([128, 198, 248, 356, 480, 615, 742, 980, 1260, 1580, 2380, 3150])
        qty = rng.randint(1, 24)
        lines.append((rate, price * qty))
    deliveries.append(lines)

def rnd(x100, mode):  # x100 = 金額×税率(整数)。税額 = x100/100 の端数処理
    q = Decimal(x100) / 100
    if mode == 'floor': return math.floor(q)
    if mode == 'ceil': return math.ceil(q)
    return int(q.quantize(Decimal('1'), rounding=ROUND_HALF_UP))

out = {'seed': 20261016, 'deliveries': len(deliveries), 'lines': sum(len(l) for l in deliveries)}
base = {8: 0, 10: 0}
for l in deliveries:
    for r, a in l: base[r] += a
out['base'] = base
out['linesByRate'] = {r: sum(1 for l in deliveries for x in l if x[0] == r) for r in (8, 10)}
out['deliveriesWithBoth'] = sum(1 for l in deliveries if {x[0] for x in l} == {8, 10})
out['roundingUnits'] = {'A': out['lines'], 'B': sum(len({x[0] for x in l}) for l in deliveries), 'C': 2}
res = {}
for mode in ('floor', 'half_up', 'ceil'):
    A = {r: sum(rnd(a * r, mode) for l in deliveries for rr, a in l if rr == r) for r in (8, 10)}
    B = {8: 0, 10: 0}
    for l in deliveries:
        s = {8: 0, 10: 0}
        for r, a in l: s[r] += a
        for r in (8, 10):
            if s[r]: B[r] += rnd(s[r] * r, mode)
    C = {r: rnd(base[r] * r, mode) for r in (8, 10)}
    res[mode] = {k: {'8': v[8], '10': v[10], 'total': v[8] + v[10]} for k, v in (('A', A), ('B', B), ('C', C))}
    res[mode]['exact'] = {r: str(Decimal(base[r] * r) / 100) for r in (8, 10)}
out['results'] = res
# 納品書ごとの切り捨てで消えた端数(円未満)の分布
lost = []
for l in deliveries:
    s = {8: 0, 10: 0}
    for r, a in l: s[r] += a
    lost.append(round(sum((s[r] * r % 100) / 100 for r in (8, 10)), 2))
out['lostPerDelivery'] = lost
print(json.dumps(out, ensure_ascii=False, indent=1))

# 図:3通りの税額(切り捨て)
f = res['floor']; vals = [('明細ごと(不可)', f['A']['total'], '#a8a8a8', '#444444'), ('納品書ごと(問67)', f['B']['total'], '#b5cc8e', '#111111'), ('請求書で1回(問65)', f['C']['total'], '#678b2c', '#ffffff')]
W, H = 720, 300; x0, x1 = 210, 640; lo = min(v[1] for v in vals) - 60; hi = max(v[1] for v in vals) + 10
def X(v): return x0 + (v - lo) / (hi - lo) * (x1 - x0)
font = "'Hiragino Sans','Yu Gothic','Meiryo',sans-serif"
s = [f"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 {W} {H}' width='{W}' height='{H}' role='img' aria-labelledby='itr-3w-t' font-family=\"{font}\">",
     f"<title id='itr-3w-t'>架空の1か月(納品20回・明細{out['lines']}行、8%と10%が混在、切り捨て)の消費税額を3通りで計算した横棒グラフ。明細ごと{f['A']['total']:,}円、納品書ごと{f['B']['total']:,}円、請求書で1回{f['C']['total']:,}円</title>",
     f"<rect width='{W}' height='{H}' fill='#fff'/>",
     "<text x='30' y='34' font-size='15' font-weight='700' fill='#111111'>同じ1か月の納品を、3通りの単位で税額にした結果(切り捨て)</text>"]
for i, (lab, v, col, tc) in enumerate(vals):
    y = 64 + i * 62
    s.append(f"<text x='{x0 - 12}' y='{y + 27}' font-size='13' fill='#111111' text-anchor='end'>{lab}</text>")
    s.append(f"<rect x='{x0}' y='{y}' width='{X(v) - x0:.1f}' height='40' fill='{col}'/>")
    s.append(f"<text x='{X(v) - 10:.1f}' y='{y + 26}' font-size='14' font-weight='700' fill='{tc}' text-anchor='end'>{v:,}円</text>")
s.append(f"<line x1='{x0}' x2='{x0}' y1='56' y2='250' stroke='#444444' stroke-width='1'/>")
s.append(f"<text x='{x0}' y='268' font-size='11' fill='#6b6b6b' text-anchor='middle'>{lo:,}円</text>")
s.append(f"<text x='{x1}' y='268' font-size='11' fill='#6b6b6b' text-anchor='end'>軸は{lo:,}円から(差を見やすくするため)</text>")
s.append("<text x='30' y='290' font-size='11' fill='#6b6b6b'>商いの帳場の計算(架空のデータ、乱数の種 20261016、税抜。scripts/figs/invoice_tax_rounding.py)</text></svg>")
p = ROOT / 'public/img/articles'; p.mkdir(parents=True, exist_ok=True)
(p / 'invoice-tax-rounding-monthly-3ways.svg').write_text(''.join(s), encoding='utf-8')

# 図2(判定図)と図3(消えた端数)。public/img/articles/ に書き出す
if True:
    # 図2 納品書ごとに切り捨てで消えた円未満の端数
    W, H = 720, 280; bx, bw, by, bh = 60, 24, 60, 150; mx = 1.4
    t = [f"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 {W} {H}' width='{W}' height='{H}' role='img' aria-labelledby='itr-lost-t' font-family=\"{font}\">",
         f"<title id='itr-lost-t'>架空の納品20回それぞれで、税率ごとに切り捨てて消えた1円未満の端数を並べた棒グラフ。0円から1.24円まで、20回の合計は{sum(lost):.1f}円。請求書で1回なら消えるのは{float(sum(Decimal(base[r]*r)/100 for r in (8,10))) - res['floor']['C']['total']:.1f}円</title>",
         f"<rect width='{W}' height='{H}' fill='#fff'/>",
         "<text x='30' y='32' font-size='15' font-weight='700' fill='#111111'>納品書ごとに切り捨てて消えた端数(円未満、8%と10%の合計)</text>"]
    for k in (0, 0.5, 1.0):
        yy = by + bh - k / mx * bh
        t.append(f"<line x1='{bx}' x2='{bx + 20 * bw}' y1='{yy:.1f}' y2='{yy:.1f}' stroke='#dcdcdc'/><text x='{bx - 8}' y='{yy + 4:.1f}' font-size='11' fill='#6b6b6b' text-anchor='end'>{k:.1f}円</text>")
    for i, v in enumerate(lost):
        h = v / mx * bh; x = bx + i * bw + 5
        t.append(f"<rect x='{x}' y='{by + bh - h:.1f}' width='{bw - 10}' height='{h:.1f}' fill='#678b2c'/>")
        if i % 2 == 0 or i == 19: t.append(f"<text x='{x + (bw - 10) / 2}' y='{by + bh + 16}' font-size='10.5' fill='#6b6b6b' text-anchor='middle'>{i + 1}</text>")
    t.append(f"<text x='{bx + 10 * bw}' y='{by + bh + 34}' font-size='11.5' fill='#444444' text-anchor='middle'>納品の回(1か月20回)</text>")
    t.append(f"<text x='{bx + 20 * bw + 16}' y='92' font-size='13' font-weight='700' fill='#557323'>20回の合計 {sum(lost):.1f}円</text>")
    t.append(f"<text x='{bx + 20 * bw + 16}' y='114' font-size='12' fill='#444444'>端数処理 37回</text>")
    t.append(f"<text x='{bx + 20 * bw + 16}' y='140' font-size='13' font-weight='700' fill='#111111'>請求書で1回なら 1.1円</text>")
    t.append(f"<text x='{bx + 20 * bw + 16}' y='162' font-size='12' fill='#444444'>端数処理 2回</text>")
    t.append("<text x='30' y='268' font-size='11' fill='#6b6b6b'>商いの帳場の計算(架空のデータ、乱数の種 20261016、税抜、切り捨て)</text></svg>")
    (p / 'invoice-tax-rounding-monthly-lost.svg').write_text(''.join(t), encoding='utf-8')
    # 判定図は国税庁 Q&A 問57・65・67 の読みを手で描いたもの(金額は上の計算値)
    (p / 'invoice-tax-rounding-monthly-decide.svg').write_text(DECIDE, encoding='utf-8')
