契約データ管理パターン
:LiTarget: 用途
契約書・更新期日・支払条件をTypeScriptで型安全に管理するパターン。
:LiSparkle: 特徴
- 契約一覧
- 期日管理
- 支払条件
- 型安全
:LiCode: 実コード(SCALE Base より自動抽出)
:LiInfo:
lib/contracts-data.tsの中身そのもの。コピペ即可。
// 契約書対応セクションのデータ層
// freeeサイン連携 + テンプレート管理 + AI修正 + 進捗トラッキング
export const FREEE_SIGN_URL = 'https://ninja-sign.com/teams/66605/documents';
export const CONTRACT_CATEGORIES = [
'業務委託契約',
'秘密保持契約 (NDA)',
'取引基本契約',
'個別契約',
'サービス利用契約',
'成果報酬契約',
'雇用契約',
'その他',
];
export type ContractStatus = 'draft' | 'reviewing' | 'sent' | 'signed' | 'canceled';
export const CONTRACT_STATUS: Record<ContractStatus, { label: string; color: string; dot: string }> = {
draft: { label: '下書き', color: 'text-gray-400 bg-gray-500/10 border-gray-500/30', dot: 'bg-gray-400' },
reviewing: { label: 'レビュー中', color: 'text-blue-400 bg-blue-500/10 border-blue-500/30', dot: 'bg-blue-400' },
sent: { label: '送付済(先方確認中)', color: 'text-orange-400 bg-orange-500/10 border-orange-500/30', dot: 'bg-orange-400' },
signed: { label: '締結完了', color: 'text-green-400 bg-green-500/10 border-green-500/30', dot: 'bg-green-400' },
canceled: { label: 'キャンセル', color: 'text-red-400 bg-red-500/10 border-red-500/30', dot: 'bg-red-400' },
};
export interface ContractTemplate {
id: string;
name: string;
category: string;
body: string;
memo: string;
createdAt: string;
updatedAt: string;
}
export interface Contract {
id: string;
title: string;
counterparty: string;
category: string;
status: ContractStatus;
freeeSignUrl: string;
owner: string;
sentDate: string;
signedDate: string;
deadline: string;
memo: string;
body: string;
lastUpdated: string;
createdAt: string;
}
const KEY_TEMPLATES = 'scale-contracts-templates';
const KEY_CONTRACTS = 'scale-contracts-contracts';
export function loadTemplates(): ContractTemplate[] {
if (typeof window === 'undefined') return [];
try {
const v = localStorage.getItem(KEY_TEMPLATES);
return v ? JSON.parse(v) : [];
} catch { return []; }
}
export function saveTemplates(x: ContractTemplate[]) {
if (typeof window === 'undefined') return;
localStorage.setItem(KEY_TEMPLATES, JSON.stringify(x));
}
export function loadContracts(): Contract[] {
if (typeof window === 'undefined') return [];
try {
const v = localStorage.getItem(KEY_CONTRACTS);
return v ? JSON.parse(v) : [];
} catch { return []; }
}
export function saveContracts(x: Contract[]) {
if (typeof window === 'undefined') return;
localStorage.setItem(KEY_CONTRACTS, JSON.stringify(x));
}
export function genId(p = 'c'): string {
return `${p}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
}
export function todayStr(): string {
return new Date().toISOString().slice(0, 10);
}
export function nowISO(): string {
return new Date().toISOString();
}
// 締結進捗の遅延判定(sent状態で deadline が今日+3日以内、あるいは 7日以上 送付済み)
export function isStalled(c: Contract): boolean {
if (c.status !== 'sent') return false;
if (c.deadline && c.deadline < todayStr()) return true;
if (c.sentDate) {
const days = Math.floor((new Date(todayStr()).getTime() - new Date(c.sentDate).getTime()) / 86400000);
if (days >= 7) return true;
}
return false;
}
// 期日までの残日数(負なら過ぎている)
export function daysUntilDeadline(deadline: string): number | null {
if (!deadline) return null;
const now = new Date(todayStr()).getTime();
const dl = new Date(deadline).getTime();
return Math.floor((dl - now) / 86400000);
}
// 初期シード: よく使う契約書の雛形を投入(初回のみ)
export function seedIfEmpty() {
if (typeof window === 'undefined') return;
if (loadTemplates().length > 0) return;
const now = nowISO();
const seeds: ContractTemplate[] = [
{
id: genId('tpl'),
name: '業務委託基本契約書(標準)',
category: '業務委託契約',
body: '第1条(目的)\n甲乙は、甲が乙に対し以下の業務を委託し、乙はこれを受託することに合意する。\n\n第2条(委託業務)\n委託する業務の詳細は別途個別契約書にて定めるものとする。\n\n第3条(契約期間)\n本契約の期間は、締結日から1年間とする。期間満了の1ヶ月前までに書面により申し出がないときは、同一条件でさらに1年間延長されるものとする。\n\n第4条(委託料)\n委託料は別途個別契約書にて定めるものとし、甲は乙に対し当該委託料を翌月末日までに支払うものとする。\n\n第5条(秘密保持)\n本契約に関連して知り得た情報を第三者に開示・漏洩してはならない。\n\n第6条(反社会的勢力の排除)\n甲乙は、反社会的勢力でないことを相互に表明し、今後も該当しないことを確約する。\n\n第7条(合意管轄)\n本契約に関する紛争は、東京地方裁判所を第一審の専属的合意管轄裁判所とする。\n\n以上、本契約の成立を証するため、本書2通を作成し、甲乙記名押印の上、各1通を保有する。',
memo: '',
createdAt: now,
updatedAt: now,
},
{
id: genId('tpl'),
name: '秘密保持契約書(NDA)',
category: '秘密保持契約 (NDA)',
body: '第1条(秘密情報の定義)\n本契約における「秘密情報」とは、開示当事者が相手方に対し開示する一切の技術上・営業上の情報であって、秘密である旨を明示したものをいう。\n\n第2条(秘密保持義務)\n受領当事者は、開示当事者の事前の書面による承諾がない限り、秘密情報を第三者に開示・漏洩してはならない。\n\n第3条(使用目的)\n受領当事者は、秘密情報を本契約の目的以外に使用してはならない。\n\n第4条(契約期間)\n本契約の有効期間は、締結日から2年間とする。ただし、秘密保持義務は本契約終了後も3年間存続するものとする。\n\n第5条(合意管轄)\n本契約に関する紛争は、東京地方裁判所を第一審の専属的合意管轄裁判所とする。\n\n以上、本契約の成立を証するため、本書2通を作成し、甲乙記名押印の上、各1通を保有する。',
memo: '',
createdAt: now,
updatedAt: now,
},
{
id: genId('tpl'),
name: '成果報酬型 個別契約書',
category: '成果報酬契約',
body: '第1条(業務内容)\n甲は乙に対し、以下の業務を委託する。\n・対象業界:\n・対象サービス:\n・トスアップ条件:\n\n第2条(成果報酬)\n1. 成果定義:有効商談1件\n2. 報酬単価:1件あたり金◯◯◯円(税別)\n3. 支払条件:当月末締・翌月末日振込\n\n第3条(有効商談の定義)\n以下の条件を全て満たすものを「有効商談」とする。\n(1)\n(2)\n\n第4条(契約期間)\n本契約の期間は◯年◯月◯日から◯年◯月◯日までとする。\n\n第5条(合意管轄)\n東京地方裁判所を第一審の専属的合意管轄裁判所とする。',
memo: 'SCALEのBDR成果報酬の標準フォーマット',
createdAt: now,
updatedAt: now,
},
];
saveTemplates(seeds);
}
:LiFolder: ソースファイルのパス
/Users/oogushiyuuki/Library/CloudStorage/GoogleDrive-y-ogushi@scale-group.co.jp/マイドライブ/AI/scale-base/lib/contracts-data.ts
:LiHandPointer: 使い方
対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。
:LiAlertCircle: 注意事項
- 依存パッケージを忘れず追加