作品として見る 生成された文書そのもの — 切り替えも開閉もそのまま動きます
作品として見る 生成された文書そのもの — 切り替えも開閉もそのまま動きます
コピーして貼るか、ファイルに保存して渡す。ネットにつながらない環境には「全部入り」を 1 つ渡せば足りる。
このパネルの中身を、URL の先にあるファイルの中身まで含めて 1 つにまとめた Markdown — 大きいのでファイルで渡す(コピーは 32KB まで)
「売掛金の滞留・回収台帳」でHTML資料を作ってください。この型は実行できるので、版面を組み直す必要はありません — 必要なのは中身です。 ## この型がすること 滞留日数別の未回収残高と、請求単位の担当者・次の回収行動を優先づける。 ## 渡す必要があるもの - 請求ID、顧客、期限超過日数、残高、担当者、次の回収行動、状況を用意する。 ## 入力の形 - `title` — string — 回収状況の結論 - `subtitle` — string? — 締め日・対象期間 - `organization` — string — 対象事業・回収チーム - `currency` — string — 残高の通貨単位 - `invoices` — array (3–12) — 請求書(滞留日数、整合する滞留区分、残高、担当、次の回収行動、状況) 入力の機械可読な検証は schema.ts が持ちます。足りない入力は推測せず質問してください。 ## 向かない場合 - 複数案の評価基準を比較して投資判断を行う場合は、加重意思決定スコアカードを使う。 ## 作り方 入力を JSON にまとめ、次を実行します。 ```bash node scripts/render.mjs invoice-collection-aging <入力.json> --theme ink -o out.html ``` 出力は 1 ファイルで完結し、外部から何も読み込みません。 ## 原本 - https://design-vault.pages.dev/designs/invoice-collection-aging/source/template.tsx - https://design-vault.pages.dev/designs/invoice-collection-aging/source/schema.ts - https://design-vault.pages.dev/designs/invoice-collection-aging/source/sample-data.json - https://design-vault.pages.dev/designs/invoice-collection-aging/sample.html
テンプレート・入力スキーマ・サンプルデータ・メタ情報。この文書はこの 4 つから生成されています。 生成された文書
/designs/invoice-collection-aging/source/template.tsx
import type { Props } from "./schema";
const CSS = String.raw`
.asset-invoice-collection-aging{font-family:var(--font-sans);color:var(--color-primary);background:var(--color-surface);max-width:var(--container-slide);margin:auto;padding:3rem 2.5rem;line-height:1.55;font-size:var(--text-body-sm)}
.asset-invoice-collection-aging *{box-sizing:border-box}.asset-invoice-collection-aging header{margin-bottom:1.25rem}.asset-invoice-collection-aging .eyebrow{font-family:var(--font-mono);font-size:var(--text-label);color:var(--color-tertiary);margin:0 0 .6rem}.asset-invoice-collection-aging h1{font-size:var(--text-h1);line-height:1.25;max-width:28em;margin:0}.asset-invoice-collection-aging .subtitle{color:var(--color-secondary);margin:.65rem 0 0}.asset-invoice-collection-aging .summary{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));border-block:1px solid var(--color-outline);margin:1.2rem 0}.asset-invoice-collection-aging .bucket{padding:.7rem .8rem;border-right:1px solid var(--color-outline);min-width:0}.asset-invoice-collection-aging .bucket:last-child{border-right:0}.asset-invoice-collection-aging .bucket span{display:block;color:var(--color-secondary);font-size:var(--text-label)}.asset-invoice-collection-aging .bucket strong{display:block;font-family:var(--font-mono);font-size:var(--text-h3);color:var(--color-primary);overflow-wrap:anywhere}.asset-invoice-collection-aging .bucket small{font-size:var(--text-label);color:var(--color-secondary)}.asset-invoice-collection-aging .table-head,.asset-invoice-collection-aging .invoice{display:grid;grid-template-columns:minmax(6rem,.75fr) minmax(8rem,1fr) minmax(5rem,.55fr) minmax(5.5rem,.65fr) minmax(8rem,1.25fr);gap:.7rem;align-items:start}.asset-invoice-collection-aging .table-head{padding:.5rem .6rem;background:var(--color-surface-subtle);font-size:var(--text-label);color:var(--color-secondary)}.asset-invoice-collection-aging .invoice{padding:.75rem .6rem;border-bottom:1px solid var(--color-outline)}.asset-invoice-collection-aging .invoice>*{min-width:0;overflow-wrap:anywhere}.asset-invoice-collection-aging code{font-family:var(--font-mono);font-size:var(--text-label)}.asset-invoice-collection-aging .customer{font-weight:600}.asset-invoice-collection-aging .amount{text-align:right;font-family:var(--font-mono);font-variant-numeric:tabular-nums}.asset-invoice-collection-aging .meta{font-size:var(--text-label);color:var(--color-secondary)}.asset-invoice-collection-aging .action{font-size:var(--text-label)}.asset-invoice-collection-aging .status{display:inline-block;margin-top:.2rem;border:1px solid var(--color-outline);padding:.05rem .35rem;color:var(--color-secondary)}.asset-invoice-collection-aging .still-svg{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@media(max-width:760px){.asset-invoice-collection-aging{padding:1.5rem 1rem}.asset-invoice-collection-aging h1{font-size:var(--text-h2)}.asset-invoice-collection-aging .summary{grid-template-columns:repeat(3,minmax(0,1fr))}.asset-invoice-collection-aging .bucket{border-bottom:1px solid var(--color-outline)}.asset-invoice-collection-aging .table-head{display:none}.asset-invoice-collection-aging .invoice{grid-template-columns:minmax(0,1fr) auto;gap:.25rem .7rem;padding:.8rem .3rem}.asset-invoice-collection-aging .customer{grid-column:1}.asset-invoice-collection-aging .amount{grid-column:2;grid-row:1;text-align:right}.asset-invoice-collection-aging .id{grid-column:1}.asset-invoice-collection-aging .aging{grid-column:2;grid-row:2;text-align:right}.asset-invoice-collection-aging .action{grid-column:1/3}.asset-invoice-collection-aging .owner{grid-column:1/3}}
@media print{.asset-invoice-collection-aging{max-width:none;padding:1rem}.asset-invoice-collection-aging .invoice{break-inside:avoid}}
`;
const bucketOrder = ["current", "1–30", "31–60", "61–90", "90+"] as const;
const bucketNames = { current: "期限内", "1–30": "1–30日", "31–60": "31–60日", "61–90": "61–90日", "90+": "90日超" };
const statusNames = { "contact-needed": "連絡要", promised: "支払約束", disputed: "異議あり", "in-progress": "対応中", escalated: "上申", paid: "入金済" };
const money = (amount: number, currency: string) => `${currency} ${new Intl.NumberFormat("ja-JP", { maximumFractionDigits: 2 }).format(amount)}`;
function Still({ data }: { data: Props }) {
const sums = Object.fromEntries(bucketOrder.map((bucket) => [bucket, data.invoices.filter((invoice) => invoice.bucket === bucket).reduce((sum, invoice) => sum + invoice.amount, 0)]));
return <svg className="still-svg" data-layout-still="true" width="1280" height="720" viewBox="0 0 1280 720" aria-hidden="true">
<rect width="1280" height="720" fill="var(--color-surface)" />
<text x="48" y="45" fontSize="14" fontFamily="sans-serif" fill="var(--color-secondary)">{data.organization} · {data.currency}</text>
<text x="48" y="87" fontSize="27" fontWeight="700" fontFamily="sans-serif" fill="var(--color-on-surface)">{data.title}</text>
<text x="48" y="123" fontSize="13" fontFamily="sans-serif" fill="var(--color-secondary)">滞留年齢別の残高と、請求ごとの担当・次の回収行動</text>
{bucketOrder.map((bucket, index) => <g key={bucket}>
<text x={50 + index * 238} y="171" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">{bucketNames[bucket]}</text>
<text x={50 + index * 238} y="204" fontSize="20" fontWeight="700" fontFamily="monospace" fill="var(--color-on-surface)">{money(sums[bucket], data.currency)}</text>
<line x1={48 + index * 238} x2={268 + index * 238} y1="220" y2="220" stroke="var(--color-outline)" />
</g>)}
{data.invoices.slice(0, 8).map((invoice, index) => {
const y = 266 + index * 51;
return <g key={invoice.id}>
<line x1="48" x2="1232" y1={y - 17} y2={y - 17} stroke="var(--color-outline)" />
<text x="52" y={y + 3} fontSize="11" fontFamily="monospace" fill="var(--color-tertiary)">{invoice.id}</text>
<text x="165" y={y + 3} fontSize="12" fontFamily="sans-serif" fill="var(--color-on-surface)">{invoice.customer}</text>
<text x="555" y={y + 3} fontSize="11" fontFamily="sans-serif" fill="var(--color-secondary)">{invoice.daysOverdue}日 · {bucketNames[invoice.bucket]}</text>
<text x="750" y={y + 3} fontSize="12" fontFamily="monospace" fill="var(--color-on-surface)">{money(invoice.amount, data.currency)}</text>
<text x="940" y={y + 3} fontSize="11" fontFamily="sans-serif" fill="var(--color-secondary)">{invoice.owner}</text>
</g>;
})}
<text x="48" y="704" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">{data.invoices.length}請求 · 合計 {money(data.invoices.reduce((sum, invoice) => sum + invoice.amount, 0), data.currency)}</text>
</svg>;
}
export default function Template(data: Props) {
const summaries = bucketOrder.map((bucket) => ({ bucket, rows: data.invoices.filter((invoice) => invoice.bucket === bucket), total: data.invoices.filter((invoice) => invoice.bucket === bucket).reduce((sum, invoice) => sum + invoice.amount, 0) }));
return <section className="asset asset-invoice-collection-aging">
<style>{CSS}</style>
<header>
<p className="eyebrow">{data.organization} · 売掛金回収 · {data.currency}</p>
<h1>{data.title}</h1>
{data.subtitle && <p className="subtitle">{data.subtitle}</p>}
</header>
<div className="summary" aria-label="滞留日数別残高">
{summaries.map(({ bucket, rows, total }) => <div className="bucket" key={bucket}><span>{bucketNames[bucket]}</span><strong>{money(total, data.currency)}</strong><small>{rows.length}件</small></div>)}
</div>
<div className="table-head" aria-hidden="true"><span>請求書</span><span>顧客</span><span>滞留</span><span>残高</span><span>次の回収行動</span></div>
{data.invoices.map((invoice) => <article className="invoice" key={invoice.id}>
<div className="id"><code>{invoice.id}</code><span className="status">{statusNames[invoice.status]}</span></div>
<div className="customer">{invoice.customer}<div className="meta">担当: {invoice.owner}</div></div>
<div className="aging">{invoice.daysOverdue === 0 ? "期限内" : `${invoice.daysOverdue}日`}<div className="meta">{bucketNames[invoice.bucket]}</div></div>
<div className="amount">{money(invoice.amount, data.currency)}</div>
<div className="action">{invoice.nextAction}</div>
</article>)}
<Still data={data} />
</section>;
}
/designs/invoice-collection-aging/source/schema.ts
import { z } from "zod";
const text = z.string().trim().min(1).max(160);
const short = z.string().trim().min(1).max(48);
const bucketSchema = z.enum(["current", "1–30", "31–60", "61–90", "90+"]);
const invoiceSchema = z.object({
id: short.describe("請求書の一意な識別子"),
customer: short.describe("顧客または取引先"),
daysOverdue: z.number().int().min(0).max(365).describe("支払期限からの経過日数"),
bucket: bucketSchema.describe("経過日数から決まる滞留区分"),
amount: z.number().positive().describe("未回収残高(通貨単位はヘッダーで指定)"),
owner: short.describe("回収の担当者"),
nextAction: text.describe("次に実施する具体的な回収アクション"),
status: z.enum(["contact-needed", "promised", "disputed", "in-progress", "escalated", "paid"]).describe("回収状況"),
}).strict();
const expectedBucket = (days: number) => days === 0 ? "current" : days <= 30 ? "1–30" : days <= 60 ? "31–60" : days <= 90 ? "61–90" : "90+";
export const schema = z.object({
title: text.describe("回収状況の結論"),
subtitle: text.optional().describe("締め日・対象期間"),
organization: short.describe("対象事業・回収チーム"),
currency: short.describe("残高の通貨単位"),
invoices: z.array(invoiceSchema).min(3).max(12).describe("請求単位の回収対象一覧"),
}).strict().superRefine((data, ctx) => {
const ids = data.invoices.map(({ id }) => id);
if (new Set(ids).size !== ids.length) {
ctx.addIssue({ code: "custom", path: ["invoices"], message: "請求書IDは一意にすること" });
}
data.invoices.forEach((invoice, index) => {
if (invoice.bucket !== expectedBucket(invoice.daysOverdue)) {
ctx.addIssue({ code: "custom", path: ["invoices", index, "bucket"], message: "滞留区分は経過日数に一致させること" });
}
});
});
export type Props = z.infer<typeof schema>;
/designs/invoice-collection-aging/source/meta.yaml
id: invoice-collection-aging
name:
ja: 売掛金の滞留・回収台帳
en: Invoice Collection Aging Register
purpose:
ja: 滞留日数別の未回収残高と、請求単位の担当者・次の回収行動を優先づける。
en: Prioritize overdue receivables by aging bucket and assign the next collection action to each invoice.
status: published
still: ./still.png
category: operations
catalogOrder: 835
jobs:
- prioritize-diagnose
- assign-governance
useCases:
- kpi
- status
layoutKinds:
- table
- stack
tags:
- accounts-receivable
- collections
- aging
- cash-flow
- responsive
whenToUse:
- ja: 未回収残高を滞留日数で区分し、担当者ごとの具体的な次の連絡や上申を決めるとき。
en: Use to bucket overdue balances by age and assign the next collection contact or escalation.
avoidWhen:
- ja: 複数案の評価基準を比較して投資判断を行う場合は、加重意思決定スコアカードを使う。
en: Use a weighted decision scorecard to compare criteria for an investment decision.
requiredInputs:
- ja: 請求ID、顧客、期限超過日数、残高、担当者、次の回収行動、状況を用意する。
en: Provide invoice ID, customer, days overdue, balance, owner, next collection action, and status.
relatedLayouts:
- action-register
- waterfall
dataSchema:
title: string — 回収状況の結論
subtitle: string? — 締め日・対象期間
organization: string — 対象事業・回収チーム
currency: string — 残高の通貨単位
invoices: array (3–12) — 請求書(滞留日数、整合する滞留区分、残高、担当、次の回収行動、状況)
sources: []
/designs/invoice-collection-aging/source/sample-data.json
{
"title": "90日超の延滞残高に管理者を割り当て、約束日を再確認する",
"subtitle": "2026年9月締め · 回収予定と次の連絡を確認",
"organization": "Northstar Wholesale",
"currency": "USD",
"invoices": [
{ "id": "INV-1048", "customer": "Cedar Works", "daysOverdue": 12, "bucket": "1–30", "amount": 18400, "owner": "Mina Sato", "nextAction": "9/26までに支払予定日を確認する", "status": "promised" },
{ "id": "INV-1072", "customer": "Harbor Medical", "daysOverdue": 46, "bucket": "31–60", "amount": 32750, "owner": "Ren Ito", "nextAction": "納品証明を添えて経理責任者へ連絡する", "status": "disputed" },
{ "id": "INV-0991", "customer": "Alpine Transit", "daysOverdue": 108, "bucket": "90+", "amount": 51200, "owner": "Aki Mori", "nextAction": "営業責任者同席で支払計画を合意する", "status": "escalated" },
{ "id": "INV-1103", "customer": "Lumen Foods", "daysOverdue": 0, "bucket": "current", "amount": 9200, "owner": "Mina Sato", "nextAction": "期限前の請求受領を確認する", "status": "contact-needed" }
]
}