読み込み中
豆知識
This document defines the required JSONB content structure for each document type in Solo Boss Cloud.
CRITICAL: When creating documents via SQL or API, the content field must match these exact schemas for the frontend renderers to display data correctly.
content.metadataAll metadata keys must be camelCase. The backend's case-conversion middleware converts top-level API response fields but does not recursively convert JSONB content. Storing snake_case keys breaks frontend category detection and badge display.
| WRONG (snake_case) | CORRECT (camelCase) |
|---|---|
payment_type |
paymentType |
payment_type_label |
paymentTypeLabel |
employee_name |
employeeName |
client_name |
clientName |
Enforcement (automatic): A PostgreSQL trigger trg_normalize_document_metadata_keys fires BEFORE INSERT OR UPDATE on the documents table and auto-converts any snake_case keys listed above to camelCase. This means even if you insert via SQL with snake_case keys, they will be normalized automatically.
Frontend safety net: salary-category-detector.ts checks both camelCase and snake_case keys as a double fallback. But the trigger is the primary guard.
Document Type: travel
{
"document_title": "出張報告書",
"company": "サンプル合同会社",
"basic": {
"reporter": "代表太郎",
"report_date": "2026年1月25日",
"period": "2025年12月17日〜2025年12月21日(4泊5日)",
"destination": "〇〇県・〇〇村・高原の拠点エリア"
},
"purposes": {
"options": {
"視察・現地調査": true,
"現地打合せ": true
},
"detail": "詳細な出張目的の説明..."
},
"itinerary": [
{
"date": "12/17",
"time": "終日",
"contact": "-",
"activity": "東京から高原拠点へ移動"
}
],
"outcomes": {
"achievements": ["成果1", "成果2"],
"next_actions": ["次のアクション1", "次のアクション2"]
},
"expenses": {
"items": [
{
"date": "12/17",
"category": "交通費",
"description": "JR電車(往路)",
"jpy": 1990,
"payment": "個人カード",
"receipt": true,
"remarks": ["赤羽→長野原草津口"]
}
],
"summary": {
"grand_total_j_p_y": 52800,
"personal_expenses": 32301,
"daily_allowance": 25000,
"company_paid": 0,
"reimbursement_amount": 52800
}
},
"signature": {
"date": "2026年1月25日",
"name_and_title": "代表社員 代表太郎",
"inkan_image": "https://...",
"signing_mode": "inkan"
},
"linked_documents": {
"expense_claims": ["uuid-of-claims-doc"],
"salary_slip": "uuid-of-salary-slip",
"payment_statement": "uuid-of-payment-doc"
}
}
| Section | Required Fields | Notes |
|---|---|---|
| basic | reporter, report_date, period, destination | Displayed in "1. 基本情報" |
| purposes | options (object), detail (string) | Displayed in "2. 出張目的・概要" |
| itinerary | date, time, contact, activity | Displayed in "3. 行程・訪問先" |
| outcomes | achievements, next_actions (arrays) | Displayed in "4. 出張成果・今後の予定" |
| expenses.items | date, category, description, jpy, payment | Displayed in "5. 支出内訳" |
Document Type: claims
{
"basicInfo": {
"applicant": "代表太郎",
"position": "代表社員",
"company": "サンプル合同会社",
"applicationDate": "2026年1月25日",
"period": "2025年12月17日〜2025年12月21日"
},
"expenses": [
{
"date": "20251110",
"category": "旅費交通費",
"vendor": "JR東日本",
"purpose": "JR電車運賃(赤羽→長野原草津口)",
"amount": 1990,
"currency": "JPY",
"exchangeRate": 1,
"allocationRate": 100,
"paymentMethod": "個人カード",
"taxCategory": "課税 10%",
"businessPurpose": "高原拠点の改修出張のための交通費"
}
],
"summary": {
"itemCount": 13,
"totalAmount": 52800,
"totalExpenseAmount": 52800,
"reimbursementAmount": 52800,
"personalPayment": 32301,
"companyAllowance": 25000,
"companyDirectPayment": 0,
"totalAttachments": 0
},
"signature": {
"date": "2026年1月25日",
"nameAndTitle": "代表社員 代表太郎"
},
"linkedDocuments": {
"travelReport": "uuid-of-travel-doc",
"salarySlip": "uuid-of-salary-slip",
"paymentStatement": "uuid-of-payment-doc"
}
}
| Section | Required Fields | Notes |
|---|---|---|
| basicInfo | applicant, position, company, applicationDate, period | Displayed in "1. 基本情報" |
| expenses[] | date, category, vendor, purpose, amount, currency | Each expense row |
| summary | totalAmount, reimbursementAmount | Totals section |
IMPORTANT: Use basicInfo NOT applicant at root level!
Document Type: payment
{
"paymentInfo": {
"date": "2026年1月25日",
"issueDate": "2026年1月25日",
"recipient": "代表太郎",
"companyName": "サンプル合同会社",
"bankInfo": "三菱UFJ銀行 駒沢大学駅前支店 普通 0067223",
"amount": 52800
},
"settlementDetails": {
"description": "高原拠点の改修出張 2025/11/10-14 経費精算",
"items": [
{
"item": "交通費",
"content": "JR電車往復・NEXCO高速道路・鬼押し出しハイウェイ",
"amount": 10830
}
],
"totalAmount": 52800,
"totalDescription": "合計支払額",
"amount": 52800
},
"referenceInfo": {
"applicationDate": "2026年1月25日",
"applicant": "代表太郎",
"method": "銀行振込",
"status": "支払済"
},
"metadata": {
"paymentType": "reimbursement",
"paymentTypeLabel": "経費精算"
},
"signature": {
"date": "2026年1月25日",
"name": "経理担当"
},
"notes": ["この支払明細書は給与明細書と合わせて7年間保管してください"],
"linkedDocuments": {
"travelReport": "uuid-of-travel-doc",
"expenseClaims": ["uuid-of-claims-doc"],
"salarySlip": "uuid-of-salary-slip"
}
}
| Section | Required Fields | Notes |
|---|---|---|
| paymentInfo | date, recipient, companyName, bankInfo, amount | Displayed in "1. 支払情報" |
| settlementDetails.items[] | item, content, amount | Displayed in "2. 精算内容" |
| referenceInfo | applicationDate, method, status | Displayed in "3. 参照情報" |
Document Type: salary_slip
{
"companyName": "サンプル合同会社",
"payPeriod": "2025年12月分",
"payday": "2026年1月25日",
"employee": {
"name": "代表太郎",
"department": "代表社員"
},
"earnings": [
{
"item": "出張精算",
"amount": 52800,
"notes": "高原拠点の改修出張 2025/11/10-14\n交通費・燃料費・消耗品費・会議費・接待交際費・日当"
}
],
"deductions": [],
"totalEarnings": 52800,
"totalDeductions": 0,
"netPay": 52800,
"paymentStatus": {
"isPaid": true,
"paidDate": "2026-01-25"
},
"metadata": {
"paymentType": "reimbursement",
"paymentTypeLabel": "経費精算"
},
"signature": {
"date": "2026-01-25",
"name": "代表太郎"
},
"linkedDocuments": {
"travelReport": "uuid-of-travel-doc",
"expenseClaims": ["uuid-of-claims-doc"],
"paymentStatement": "uuid-of-payment-doc"
}
}
| Field | Type | Notes |
|---|---|---|
| netPay | number | MUST be at root level - used by dock to display amount |
| earnings[] | array | Each earnings item |
| deductions[] | array | Each deduction item (empty for reimbursements) |
| totalEarnings | number | Sum of earnings |
| totalDeductions | number | Sum of deductions |
CRITICAL: Use camelCase (netPay, payPeriod, totalEarnings) NOT snake_case!
document_relationshipsWhen linking documents, create bidirectional relationships:
-- Travel → Claims
INSERT INTO document_relationships
(parent_document_id, child_document_id, relationship_type, created_by)
VALUES
('travel-uuid', 'claims-uuid', 'expense_claim', 'user-uuid'),
('travel-uuid', 'claims-uuid', 'expense_claim_source', 'user-uuid');
-- Travel → Payment
INSERT INTO document_relationships VALUES
('travel-uuid', 'payment-uuid', 'payment_voucher', 'user-uuid'),
('travel-uuid', 'payment-uuid', 'payment_source', 'user-uuid');
-- Travel → Salary Slip
INSERT INTO document_relationships VALUES
('travel-uuid', 'salary-uuid', 'salary_attachment', 'user-uuid');
-- Claims → Travel (reverse)
INSERT INTO document_relationships VALUES
('claims-uuid', 'travel-uuid', 'travel_source', 'user-uuid');
-- Claims → Payment
INSERT INTO document_relationships VALUES
('claims-uuid', 'payment-uuid', 'payment_voucher', 'user-uuid');
-- Claims → Salary Slip
INSERT INTO document_relationships VALUES
('claims-uuid', 'salary-uuid', 'salary_source', 'user-uuid');
-- Payment → Travel (reverse)
INSERT INTO document_relationships VALUES
('payment-uuid', 'travel-uuid', 'travel_source', 'user-uuid');
-- Payment → Salary Slip
INSERT INTO document_relationships VALUES
('payment-uuid', 'salary-uuid', 'salary_source', 'user-uuid');
-- Salary Slip → All (reverse links)
INSERT INTO document_relationships VALUES
('salary-uuid', 'travel-uuid', 'travel_source', 'user-uuid'),
('salary-uuid', 'claims-uuid', 'expense_claim', 'user-uuid'),
('salary-uuid', 'payment-uuid', 'payment_voucher', 'user-uuid');
| Type | Description |
|---|---|
expense_claim |
Links to expense claims document |
expense_claim_source |
Source document for expense claim |
payment_voucher |
Links to payment document |
payment_source |
Source document for payment |
salary_attachment |
Links to salary slip |
salary_source |
Source document for salary slip |
travel_source |
Source travel report |
bank_statementsINSERT INTO bank_statements
(id, user_id, transaction_date, description, balance, deposit_amount, withdrawal_amount)
VALUES
(gen_random_uuid(), 'user-uuid', 20260125, '経費精算 高原拠点の改修出張', 0, 0, 52800);
NOTE: transaction_date is INTEGER format: YYYYMMDD (e.g., 20260125)
document_transaction_linksINSERT INTO document_transaction_links
(user_id, document_id, bank_transaction_id, linkage_type, allocated_amount, match_method, created_by)
VALUES
('user-uuid', 'payment-uuid', 'bank-tx-uuid', 'payment', 52800, 'manual', 'user-uuid');
// WRONG - uses snake_case
{
"net_pay": 52800,
"pay_period": "2025年12月分",
"total_earnings": 52800
}
// CORRECT - uses camelCase
{
"netPay": 52800,
"payPeriod": "2025年12月分",
"totalEarnings": 52800
}
// WRONG - applicant at root
{
"applicant": { "name": "代表太郎" },
"title": "立替経費申請書"
}
// CORRECT - uses basicInfo
{
"basicInfo": {
"applicant": "代表太郎",
"position": "代表社員",
"applicationDate": "2026年1月25日"
}
}
// WRONG - uses tripInfo
{
"tripInfo": {
"destination": "群馬県",
"period": "11/10-14"
}
}
// CORRECT - uses basic
{
"basic": {
"reporter": "代表太郎",
"report_date": "2026年1月25日",
"period": "2025年12月17日〜2025年12月21日",
"destination": "〇〇県・〇〇村"
}
}
// WRONG - no date field
{
"expenses": {
"items": [
{ "category": "交通費", "description": "JR", "jpy": 1990 }
]
}
}
// CORRECT - includes date
{
"expenses": {
"items": [
{ "date": "12/17", "category": "交通費", "description": "JR", "jpy": 1990 }
]
}
}
-- WRONG - string format
INSERT INTO bank_statements (transaction_date) VALUES ('2026-01-25');
-- CORRECT - integer format YYYYMMDD
INSERT INTO bank_statements (transaction_date) VALUES (20260125);
Before creating documents, verify:
netPay at root level for salary slipsbasicInfo object for claims (not flat applicant fields)basic object for travel (not tripInfo)date field in all expense itemslinkedDocuments in content JSON matches document_relationships tableDefault test user: 00000000-0000-4000-8000-000000000001
最終更新: January 2026
読み込み中
豆知識