"""記事「販売管理パッケージが合わないのはどこ?20項目で照合」(cms/articles/sales-package-fit-gap.md)の判定表・件数・図。

1. 卸の商習慣20項目を、B社(インストール型の販売管理パッケージ)の公開資料に当てた判定を数える。
   判定は3つ。
     標準        … 機能紹介に、伝票や帳票の機能としてそのまま使える形で書かれている
     設定で対応  … 台帳・自社の設定・帳票レイアウタなど、登録や設定をして使う形で書かれている
     記載なし    … 公開資料に記述が見当たらない(「できない」とは限らない)
   当てた資料(2026-10-16 に開いて確かめた):機能紹介(導入・設定/見積・受注・売上/請求・回収/発注・仕入・支払/
   在庫・生産/便利な機能・サービス/機能一覧/メニュー一覧/印刷帳票一覧)、主要スペック、サポート情報「見積書の作成」。
   判定の根拠の引用は ITEMS の4列目(原文の短い引用。記載なしは何を探したか)。
2. 主要スペックの上限に当たる条件を計算する。
   伝票は見積・受注・発注・売上・仕入・入金・支払・生産・出庫・倉庫移動の合計で60万明細。
   1日300明細・年245日として、使う伝票の種類が1・2・3種類のときに上限に届く年数。
   1伝票100行:n 行の注文は ceil(n/100) 枚の伝票に分かれる。台帳は得意先・仕入先・商品・倉庫の合計25万件。
使い方: python scripts/figs/sales_package_fit_gap.py   (fields/sales-management-app/ で実行。標準ライブラリだけ)
出力: public/img/articles/sales-package-fit-gap-matrix.svg(20項目の判定の図。frontmatter の image とアイキャッチ)、
      scripts/figs/.out/ に記事へ貼るインラインSVG 2枚(spfg_fig_counts.svg・spfg_fig_years.svg)、数値は標準出力。
"""
import math, pathlib, collections

ROOT = pathlib.Path(__file__).resolve().parents[2]
OUTDIR = ROOT / 'scripts/figs/.out'
INK, GRAY, LINE, ACC, ACC_TXT, SOFT, MID, NONE = '#111111', '#6b6b6b', '#dcdcdc', '#678b2c', '#557323', '#eef3e6', '#b5cc8e', '#ececec'
FONT = "'Hiragino Sans','Yu Gothic','Meiryo',sans-serif"
STD, CFG, NA = '標準', '設定で対応', '記載なし'
COLOR = {STD: (ACC, '#fff'), CFG: (MID, INK), NA: (NONE, INK)}

# (区分, 項目, 判定, 根拠)
ITEMS = [
    ('単価', '得意先×商品の単価', CFG, '「商品価格表では、得意先別商品別…での価格設定が可能」(導入・設定)'),
    ('単価', '1商品に複数の売価を持ち、得意先で使い分ける', CFG, '「商品単価が複数設定できるので、得意先ごとに使用する単価を使い分けることも可能」(導入・設定)'),
    ('単価', '掛け率', CFG, '「得意先台帳に登録した掛率が表示されます」(サポート情報)。何に掛けるかは記載なし'),
    ('単価', '特値の期間(いつからいつまで)', NA, '単価・価格表の記述に期間の指定なし'),
    ('単価', '単価改定の適用開始日', NA, 'メニューに「単価一括変更」はあるが、適用日の指定の記述なし'),
    ('単価', '前回の売価の参照', STD, '過去の「得意先」「数量」「単価」を表示する「単価履歴表示」(見積・受注・売上)'),
    ('物流', '分納と受注残', STD, '「未出荷」「全出荷」「一部出荷」「訂正出荷」「受注取消」の5つの出荷状態(見積・受注・売上)'),
    ('物流', 'メーカー直送の発注', STD, '「発注伝票で直送先や自社倉庫を発送先として指定」(発注・仕入・支払。プロフェッショナル以上)'),
    ('物流', '直送の仕入と売上を1つの受注から同時に作る', NA, '仕入伝票は「発注伝票の『受け入れ』ボタン」で作るとあるが、売上との連動の記述なし'),
    ('物流', '預かり在庫(売上済みで自社倉庫に残る品)', NA, '在庫・生産の記述にセット商品・生産・複数倉庫はあるが、預かりの記述なし'),
    ('物流', '納入先(現場・支店)ごとの届け先', CFG, 'メニューに「納入先台帳」、帳票に「納入先別売上分析表」'),
    ('物流', 'ロット・賞味期限', NA, '在庫・生産と主要スペックに記述なし'),
    ('請求・帳票', '締め日が違う得意先の一括請求', CFG, '「得意先台帳に設定した締めグループに基づき、同一締日の得意先の請求書を一括発行」(請求・回収)'),
    ('請求・帳票', '締め請求と都度請求の混在', STD, '「締め請求・都度請求の両方に対応」(請求・回収)'),
    ('請求・帳票', '本社など別の請求先への一括請求', CFG, '「売掛金は請求先で一括管理し、請求書も請求先に一括発行」など3通り。うち1通りは「インボイスの要件に非対応」(請求・回収の図)'),
    ('請求・帳票', '適格請求書と税率ごとの税額', STD, '「税率ごとの消費税額を自動で集計して出力できます」(請求・回収)'),
    ('請求・帳票', '消費税の端数処理の方法', CFG, '「伝票の付番方法や端数処理方法など…柔軟な設定ができます」(導入・設定)'),
    ('請求・帳票', '税額を計算する単位(納品書ごとか請求書で1回か)', NA, '端数処理の方法の記述はあるが、計算の単位の記述なし'),
    ('請求・帳票', '返品・値引きの適格返還請求書', NA, '請求・回収と印刷帳票一覧に記述なし'),
    ('請求・帳票', '今の帳票の形(自社の様式)で出す', CFG, '帳票レイアウタで「項目の位置、罫線処理、背景色などを自由に設定」(便利な機能・サービス)'),
]
GROUPS = ['単価', '物流', '請求・帳票']

# 主要スペックの上限
SLIP_CAP, MASTER_CAP, LINES_PER_SLIP = 600_000, 250_000, 100
DAILY, DAYS = 300, 245


def counts():
    c = collections.OrderedDict((g, collections.Counter()) for g in GROUPS)
    for g, _, j, _ in ITEMS:
        c[g][j] += 1
    return c


def years(kinds):
    return SLIP_CAP / (DAILY * DAYS * kinds)


def t(x, y, s, size=11, fill=INK, anchor='start', weight=400):
    return f"<text x='{x}' y='{y}' font-size='{size}' fill='{fill}' text-anchor='{anchor}' font-weight='{weight}'>{s}</text>"


def legend(x, y):
    out = []
    for j in (STD, CFG, NA):
        bg, _ = COLOR[j]
        out.append(f"<rect x='{x}' y='{y}' width='12' height='12' fill='{bg}' stroke='{LINE}'/>" + t(x + 17, y + 10, j))
        x += 100
    return ''.join(out)


def fig_matrix():
    """20項目を区分ごとに並べ、判定で塗り分けた図(public に置く1枚)"""
    W, rowh, top = 760, 26, 64
    per = [[it for it in ITEMS if it[0] == g] for g in GROUPS]
    H = top + max(len(p) for p in per) * rowh + 70
    colw = (W - 40) / 3
    s = [f"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 {W} {H}' width='{W}' height='{H}' font-family=\"{FONT}\">",
         f"<rect width='{W}' height='{H}' fill='#fff'/>", t(20, 26, '卸の商習慣20項目をB社の公開資料に当てた判定', 15, INK, weight=700), legend(430, 16)]
    c = counts()
    for gi, (g, items) in enumerate(zip(GROUPS, per)):
        x = 20 + gi * colw
        k = c[g]
        s.append(t(x, top - 10, f"{g}({len(items)}項目:標準{k[STD]}・設定{k[CFG]}・記載なし{k[NA]})", 11.5, INK, weight=700))
        for i, (_, name, j, _) in enumerate(items):
            bg, fg = COLOR[j]
            y = top + i * rowh
            s.append(f"<rect x='{x}' y='{y}' width='{colw - 10:.0f}' height='{rowh - 4}' fill='{bg}'/>")
            label = name if len(name) <= 19 else name[:18] + '…'
            s.append(t(x + 7, y + 15, label, 10.5, fg, weight=700 if j == STD else 400))
    s.append(t(20, H - 34, f"件数:標準{sum(v[STD] for v in c.values())}・設定で対応{sum(v[CFG] for v in c.values())}・記載なし{sum(v[NA] for v in c.values())}。記載なしは公開資料に記述が見当たらないもので、できないとは限らない", 10.5, GRAY))
    s.append(t(20, H - 16, '2026年10月16日に公開資料を確認。出典:商いの帳場の照合(scripts/figs/sales_package_fit_gap.py)', 10.5, GRAY))
    s.append('</svg>')
    return '\n'.join(s), W, H


def fig_counts():
    """区分ごとの判定の件数(積み上げ横棒、インライン)"""
    W, H = 700, 200
    unit = 52
    s = [f"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 {W} {H}' width='{W}' height='{H}' role='img' aria-labelledby='spfg1-title' font-family=\"{FONT}\" style='max-width:100%;height:auto'>"]
    c = counts()
    title = '判定の件数の積み上げ横棒。' + '、'.join(f"{g}は標準{c[g][STD]}・設定{c[g][CFG]}・記載なし{c[g][NA]}" for g in GROUPS)
    s.append(f"<title id='spfg1-title'>{title}</title><rect width='{W}' height='{H}' fill='#fff'/>")
    s.append(legend(150, 12))
    for i, g in enumerate(GROUPS):
        y = 44 + i * 44
        s.append(t(140, y + 17, f"{g}({sum(c[g].values())})", 11.5, INK, 'end', 700))
        x = 150
        for j in (STD, CFG, NA):
            n = c[g][j]
            if not n:
                continue
            bg, fg = COLOR[j]
            s.append(f"<rect x='{x}' y='{y}' width='{n * unit - 2}' height='26' fill='{bg}'/>" + t(x + (n * unit - 2) / 2, y + 17, n, 11.5, fg, 'middle', 700))
            x += n * unit
    tot = collections.Counter()
    for v in c.values():
        tot.update(v)
    s.append(t(20, H - 18, f"計{sum(tot.values())}項目。出典:B社の公開資料(2026年10月16日確認)、商いの帳場の照合", 10.5, GRAY))
    s.append('</svg>')
    return ''.join(s)


def fig_years():
    """60万明細に届く年数(使う伝票の種類ごと、インライン)"""
    W, H = 700, 220
    rows = [('売上だけ', 1), ('受注+売上', 2), ('受注+売上+発注', 3)]
    maxy, x0, x1 = 10, 200, 640
    sx = lambda v: x0 + (x1 - x0) * v / maxy
    s = [f"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 {W} {H}' width='{W}' height='{H}' role='img' aria-labelledby='spfg2-title' font-family=\"{FONT}\" style='max-width:100%;height:auto'>"]
    title = '60万明細に届く年数の横棒。' + '、'.join(f"{r}で約{years(k):.1f}年" for r, k in rows)
    s.append(f"<title id='spfg2-title'>{title}</title><rect width='{W}' height='{H}' fill='#fff'/>")
    for v in range(0, maxy + 1, 2):
        s.append(f"<line x1='{sx(v):.1f}' x2='{sx(v):.1f}' y1='20' y2='160' stroke='{LINE}'/>" + t(f"{sx(v):.1f}", 176, f"{v}年", 10.5, GRAY, 'middle'))
    for i, (r, k) in enumerate(rows):
        y = 30 + i * 44
        yr = years(k)
        s.append(t(190, y + 18, r, 11.5, INK, 'end', 700))
        s.append(f"<rect x='{x0}' y='{y}' width='{sx(yr) - x0:.1f}' height='28' fill='{ACC if k == 2 else MID}'/>")
        s.append(t(f"{sx(yr) + 8:.1f}", y + 19, f"約{yr:.1f}年", 13, INK, weight=700))
    s.append(t(20, 204, '1日300明細・年245日。年数=600,000÷(300×245×伝票の種類)の計算値', 10.5, GRAY))
    s.append('</svg>')
    return ''.join(s)


if __name__ == '__main__':
    OUTDIR.mkdir(parents=True, exist_ok=True)
    pub = ROOT / 'public/img/articles'
    m, W, H = fig_matrix()
    (pub / 'sales-package-fit-gap-matrix.svg').write_text(m, encoding='utf-8')
    (OUTDIR / 'spfg_fig_counts.svg').write_text(fig_counts(), encoding='utf-8')
    (OUTDIR / 'spfg_fig_years.svg').write_text(fig_years(), encoding='utf-8')
    c = counts()
    tot = collections.Counter()
    for g, v in c.items():
        tot.update(v)
        print(g, sum(v.values()), dict(v))
    print('合計', dict(tot), 'matrix', W, H)
    for r, k in [('売上だけ', 1), ('受注+売上', 2), ('受注+売上+発注', 3)]:
        print(f'{r}: {years(k):.2f}年')
    for n in (80, 100, 101, 150, 250):
        print(f'{n}行の注文 → 伝票{math.ceil(n / LINES_PER_SLIP)}枚')
    print('台帳 得意先400+商品3,000 =', 3400, f'{3400 / MASTER_CAP:.2%}')
    for g, name, j, q in ITEMS:
        print(f'| {name} | {j} | {q} |')
