Mitsumori Cloud Datastore エンティティ設計
作成日: 2026-03-12
更新日: 2026-03-14
作成者: ClaudeCode
ステータス: Draft v0.2 — PostgreSQL設計をCloud Datastore設計に全面改訂(2026-03-14 PM決定)
関連Issue: #248
DB方針決定(2026-03-13 PM)
- Cloud SQL PostgreSQL → Cloud Datastore(Datastoreモード)
- Toriteki(gobms-465809)と同一プロジェクト・同一Datastoreインスタンスを使用
- 環境分離: Namespaceで実施(Toriteki同様)
1. 設計方針
- Kind命名:
mitsumori_<entity> (Toriteki同様のプレフィックス方式)
- Namespace分離:
- Production:
mitsumori
- Staging:
mitsumori-staging
- Dev:
mitsumori-dev
- Key設計: auto-generated integer ID(基本)/ 文字列名キー(マスター系)
- 大型フィールド:
exclude_from_indexes=["line_items", "notes", "raw_payload"](Toriteki同様)
- マルチテナント: 全エンティティに
tenant_id プロパティを付与
- 監査:
created_at / updated_at / created_by / updated_by を全エンティティに付与
- Phase 1スコープ: 見積書作成・承認フロー・Marcury提出支援に必要なKindのみ定義
2. エンティティ関係図(概念)
[mitsumori_tenants]
↓ 1:N
[mitsumori_users] --- [mitsumori_departments]
↓
[mitsumori_quotations] ←── line_items(プロパティとして埋め込み)
↓ 1:N
[mitsumori_approvals]
[mitsumori_customers] ←── 見積書の得意先参照
[mitsumori_facilities] ←── 見積書の物件参照
[mitsumori_order_source_patterns] ←── 見積書の発行元参照
[mitsumori_attachments] ←── GCS参照(見積書・承認添付)
--- Phase 2以降 ---
[mitsumori_agreements] ←── quotation_key参照
[mitsumori_orders] ←── Marcury発注受領
[mitsumori_rfq_triggers] ←── Toriteki RFQ起票ログ
3. Namespace設定
# app.py 初期化(Toriteki同様パターン)
import os
from google.cloud import datastore
_env = os.environ.get("ENV", "dev")
if _env == "production":
DATASTORE_NAMESPACE = "mitsumori"
elif _env == "staging":
DATASTORE_NAMESPACE = "mitsumori-staging"
else:
DATASTORE_NAMESPACE = "mitsumori-dev"
# 起動時ガード(Toriteki同様)
if _env == "staging" and DATASTORE_NAMESPACE != "mitsumori-staging":
raise RuntimeError("Refusing to start: staging must use mitsumori-staging namespace.")
if _env == "production" and DATASTORE_NAMESPACE != "mitsumori":
raise RuntimeError("Refusing to start: production must use mitsumori namespace.")
ds_client = datastore.Client(namespace=DATASTORE_NAMESPACE)
4. マスターKind(Phase 1)
4.1 mitsumori_tenants(テナント)
Key: 文字列名キー(例: godosangyo)
| プロパティ |
型 |
インデックス |
説明 |
code |
string |
✅ |
テナントコード(例: godosangyo) |
name |
string |
✅ |
テナント正式名 |
is_active |
bool |
✅ |
有効フラグ |
features |
dict |
❌ |
機能フラグJSON(例: {"commercial_facility": true}) |
created_at |
datetime |
✅ |
|
updated_at |
datetime |
✅ |
|
key = ds_client.key("mitsumori_tenants", "godosangyo")
ent = datastore.Entity(key=key, exclude_from_indexes=["features"])
ent.update({
"code": "godosangyo",
"name": "合同産業株式会社",
"is_active": True,
"features": {"commercial_facility": True, "construction_approval": True},
"created_at": now,
"updated_at": now,
})
4.2 mitsumori_departments(部門)
Key: 文字列名キー(例: godosangyo_G103)
| プロパティ |
型 |
インデックス |
説明 |
tenant_id |
string |
✅ |
テナントコード |
code |
string |
✅ |
部門コード(例: G103) |
name |
string |
✅ |
正式名 |
short_name |
string |
✅ |
略式名 |
representative |
string |
❌ |
代表者名 |
postal_code |
string |
❌ |
|
address1 |
string |
❌ |
住所1 |
address2 |
string |
❌ |
住所2 |
phone |
string |
❌ |
電話番号 |
default_quotation_expiry |
string |
❌ |
見積有効期限デフォルト |
approval_email_quotation |
string |
❌ |
見積承認依頼先メール |
approval_email_construction |
string |
❌ |
工事確認依頼先メール |
is_active |
bool |
✅ |
|
created_at |
datetime |
✅ |
|
updated_at |
datetime |
✅ |
|
4.3 mitsumori_users(ユーザー)
Key: auto-generated integer ID
| プロパティ |
型 |
インデックス |
説明 |
tenant_id |
string |
✅ |
|
department_code |
string |
✅ |
部門コード参照 |
user_code |
string |
✅ |
ユーザーコード |
name |
string |
✅ |
氏名 |
google_account |
string |
✅ |
Googleアカウント(認証用) |
account_type |
string |
✅ |
standard / admin / superuser |
perm_quotation_read |
bool |
✅ |
見積 閲覧(固定ON) |
perm_quotation_create |
bool |
✅ |
見積 登録 |
perm_quotation_update |
bool |
✅ |
見積 更新 |
perm_quotation_delete |
bool |
✅ |
見積 削除 |
perm_master_read |
bool |
✅ |
マスター管理 閲覧(固定ON) |
perm_master_create |
bool |
✅ |
マスター管理 登録 |
perm_master_update |
bool |
✅ |
マスター管理 更新 |
perm_master_delete |
bool |
✅ |
マスター管理 削除 |
perm_report_issue |
bool |
✅ |
帳票発行権限 |
stamp_url |
string |
❌ |
電子印鑑画像URL(帳票PDF用) |
default_order_source_pattern |
string |
❌ |
見積初期値: 発行元パターン |
default_issuer_name |
string |
❌ |
見積初期値: 発行者名 |
default_postal_code |
string |
❌ |
見積初期値: 郵便番号 |
default_address1 |
string |
❌ |
見積初期値: 住所1 |
default_address2 |
string |
❌ |
見積初期値: 住所2 |
default_phone |
string |
❌ |
見積初期値: 電話番号 |
default_fax |
string |
❌ |
見積初期値: FAX番号 |
default_customer_code |
string |
❌ |
見積初期値: 得意先 |
default_facility_code |
string |
❌ |
見積初期値: 物件名 |
default_subject |
string |
❌ |
見積初期値: 件名 |
default_grouping_keyword |
string |
❌ |
見積初期値: グルーピングキーワード |
is_active |
bool |
✅ |
|
created_at |
datetime |
✅ |
|
updated_at |
datetime |
✅ |
|
4.4 mitsumori_customers(得意先)
Key: 文字列名キー(例: godosangyo_C0001)
| プロパティ |
型 |
インデックス |
説明 |
tenant_id |
string |
✅ |
|
department_code |
string |
✅ |
所属部門 |
code |
string |
✅ |
得意先コード(自動採番) |
name |
string |
✅ |
正式名 |
short_name |
string |
✅ |
略式名 |
postal_code |
string |
❌ |
|
address1 |
string |
❌ |
|
address2 |
string |
❌ |
|
bank_account |
string |
❌ |
入金銀行口座 |
is_provisional |
bool |
✅ |
仮登録フラグ |
is_active |
bool |
✅ |
|
is_deleted |
bool |
✅ |
論理削除 |
created_at |
datetime |
✅ |
|
updated_at |
datetime |
✅ |
|
created_by |
string |
✅ |
|
updated_by |
string |
✅ |
|
4.5 mitsumori_facilities(建物/施設)
Key: 文字列名キー(例: godosangyo_B001)
| プロパティ |
型 |
インデックス |
説明 |
tenant_id |
string |
✅ |
|
department_code |
string |
✅ |
管理部門 |
code |
string |
✅ |
建物コード |
name |
string |
✅ |
建物名 |
short_name |
string |
✅ |
略称 |
postal_code |
string |
❌ |
|
address1 |
string |
❌ |
|
address2 |
string |
❌ |
|
facility_type |
string |
✅ |
区分(例: commercial / office) |
toriteki_facility_id |
string |
✅ |
Toriteki側facility_idマッピング(Phase 2連携用) |
is_active |
bool |
✅ |
|
is_deleted |
bool |
✅ |
|
created_at |
datetime |
✅ |
|
updated_at |
datetime |
✅ |
|
4.6 mitsumori_order_source_patterns(発行元パターン)
Key: 文字列名キー(例: godosangyo_OSP001)
| プロパティ |
型 |
インデックス |
説明 |
tenant_id |
string |
✅ |
|
department_code |
string |
✅ |
|
code |
string |
✅ |
発行元パターンコード |
name |
string |
✅ |
発行元パターン名 |
representative |
string |
❌ |
発行元代表者 |
postal_code |
string |
❌ |
|
address1 |
string |
❌ |
|
address2 |
string |
❌ |
|
phone |
string |
❌ |
|
fax |
string |
❌ |
|
sales_rep_code |
string |
❌ |
営業担当コード |
sales_rep_name |
string |
❌ |
営業担当名 |
is_active |
bool |
✅ |
|
is_deleted |
bool |
✅ |
|
created_at |
datetime |
✅ |
|
updated_at |
datetime |
✅ |
|
5. 見積Kind(Phase 1 中核)
5.1 mitsumori_quotations(見積)
Key: auto-generated integer ID
| プロパティ |
型 |
インデックス |
説明 |
tenant_id |
string |
✅ |
|
quotation_no |
string |
✅ |
見積番号(MITQ-YYYYMMDD-NNNNNN) |
phase |
string |
✅ |
rough(概算)/ implementation(実施) |
work_type |
string |
✅ |
業種(electrical/cleaning/security/hvac/other 等) |
work_type_raw |
string |
❌ |
work_type='other'時の原文 |
is_construction |
bool |
✅ |
工事系フラグ(工事確認フロー判定用) |
is_non_construction |
bool |
✅ |
工事以外チェックボックス(Fredy互換) |
department_code |
string |
✅ |
部門コード |
order_source_pattern_code |
string |
✅ |
発行元パターンコード |
issuer_name |
string |
❌ |
発行者名 |
postal_code |
string |
❌ |
|
address1 |
string |
❌ |
住所1 |
address2 |
string |
❌ |
住所2 |
phone |
string |
❌ |
電話番号 |
fax |
string |
❌ |
FAX番号 |
quotation_author |
string |
✅ |
見積作成者(google_account) |
grouping_keyword |
string |
✅ |
グルーピングキーワード |
customer_code |
string |
✅ |
得意先コード参照 |
custom_addressee |
bool |
❌ |
見積表示用の宛名を指定する |
addressee_name |
string |
❌ |
宛名(custom_addressee=Trueの場合) |
facility_code |
string |
✅ |
建物コード参照 |
subject |
string |
✅ |
件名 |
remarks |
string |
❌ |
備考 |
conditions |
string |
❌ |
条件 |
contract_type |
string |
✅ |
契約種別(fixed/other 等) |
amount |
int |
✅ |
税抜金額(円) |
tax_rate |
int |
✅ |
消費税率(%、例: 10) |
tax_amount |
int |
✅ |
消費税額(円) |
tax_included_amount |
int |
✅ |
税込金額(円) |
order_rate |
float |
❌ |
発注掛け率 |
order_amount |
int |
❌ |
発注合計(二次下請け、円) |
planned_start_date |
datetime |
✅ |
予定工期(開始) |
planned_end_date |
datetime |
✅ |
予定工期(終了) |
completion_scheduled_date |
datetime |
✅ |
完了予定日 |
quotation_date |
datetime |
✅ |
見積作成日 |
expiration_type |
string |
❌ |
見積有効期限種別 |
expiration_date |
datetime |
✅ |
見積有効期限 |
decision_date |
datetime |
✅ |
見積決定日 |
internal_note |
string |
❌ |
内部メモ |
approval_status |
string |
✅ |
not_requested/pending/approved/rejected |
construction_approval_status |
string |
✅ |
工事確認ステータス(工事系のみ) |
marcury_submitted_at |
datetime |
✅ |
Marcury提出日時 |
marcury_submission_ref |
string |
❌ |
Marcury提出参照情報 |
is_archived |
bool |
✅ |
アーカイブフラグ |
is_deleted |
bool |
✅ |
論理削除 |
line_items |
list |
❌ |
見積明細JSON(exclude_from_indexes) |
construction_info |
dict |
❌ |
工事情報JSON(exclude_from_indexes) |
created_at |
datetime |
✅ |
|
updated_at |
datetime |
✅ |
|
created_by |
string |
✅ |
google_account |
updated_by |
string |
✅ |
google_account |
key = ds_client.key("mitsumori_quotations")
ent = datastore.Entity(
key=key,
exclude_from_indexes=["line_items", "construction_info", "remarks", "conditions", "internal_note"]
)
line_items プロパティ構造(JSON)
[
{
"line_no": 1,
"is_summary": false,
"name": "スライド式ラック解体・移設作業",
"outline": "",
"business_code": "E01",
"quantity": 30,
"unit": "台",
"step_factor": null,
"headcount": null,
"unit_price": 2800,
"list_price": null,
"amount": 84000,
"tax_rate": 10,
"tax_amount": 8400,
"tax_included_amount": 92400,
"ratio": null,
"remarks": "",
"link_to_g_report": false
}
]
Marcuryクリップボード出力フォーマット(タブ区切り):
品名[TAB]数量[TAB]単位[TAB][空][TAB][空][TAB]単価[TAB]金額[TAB]消費税[TAB]
⚠️ 実装必須: このフォーマットはMarcury側仕様に完全依存(変更不可)。
construction_info プロパティ構造(JSON)
{
"info1": {
"confirmation_no": 1,
"construction_no": "K-2026-001",
"created_date": "2026-03-14",
"author": "担当者名",
"site_name": "工事現場名称",
"site_address": "工事現場所在地",
"construction_name": "工事名称",
"construction_manager": "工事担当者名",
"technical_supervisor": "配置技術者名",
"planned_start_date": "2026-04-01",
"planned_end_date": "2026-06-30",
"construction_type": "electrical",
"order_rank": "一次",
"waste_generated": false,
"waste_stored": false,
"waste_electronic_manifest": false,
"outline": "工事概要テキスト",
"notes": "留意事項テキスト"
},
"info2": {
"client_name": "注文者会社名",
"contract_amount_excl_tax": 5000000,
"payment_terms": "月末締め翌月払い",
"order_amount_total": 3500000,
"order_rate": 70.0,
"gross_profit_rate": 30.0,
"branch_management_rate": 5.0,
"branch_profit": 250000,
"construction_dept_comment": "",
"branch_opinion": ""
},
"subcontractors": [
{
"name": "下請業者名",
"amount": 1000000,
"tax_rate": 10,
"tax_included_amount": 1100000,
"payment_terms": "月末締め翌月払い",
"work_content": "電気工事",
"business_code": "E01",
"remarks": ""
}
]
}
6. 承認Kind(Phase 1)
6.1 mitsumori_approvals(承認)
Key: auto-generated integer ID
| プロパティ |
型 |
インデックス |
説明 |
tenant_id |
string |
✅ |
|
approval_type |
string |
✅ |
quotation(見積承認)/ construction(工事確認) |
status |
string |
✅ |
not_requested/pending/approved/rejected/confirmed |
quotation_key_id |
int |
✅ |
対象見積のDatastore Key ID |
quotation_no |
string |
✅ |
対象見積番号(表示用) |
department_code |
string |
✅ |
|
customer_name |
string |
✅ |
得意先名(表示用) |
facility_name |
string |
✅ |
物件名(表示用) |
subject |
string |
✅ |
件名(表示用) |
requester |
string |
✅ |
依頼者 google_account |
requested_at |
datetime |
✅ |
依頼日時 |
deadline |
datetime |
✅ |
承認締切日 |
approver |
string |
✅ |
承認者 google_account |
acted_at |
datetime |
✅ |
承認/否認日時 |
comment |
string |
❌ |
承認者コメント |
requester_note |
string |
❌ |
依頼者備考 |
created_at |
datetime |
✅ |
|
updated_at |
datetime |
✅ |
|
key = ds_client.key("mitsumori_approvals")
ent = datastore.Entity(key=key, exclude_from_indexes=["comment", "requester_note"])
7. 添付資料Kind(Phase 1)
7.1 mitsumori_attachments(添付資料)
Key: auto-generated integer ID
| プロパティ |
型 |
インデックス |
説明 |
tenant_id |
string |
✅ |
|
owner_kind |
string |
✅ |
quotation / approval(Phase 2+: agreement) |
owner_key_id |
int |
✅ |
対象エンティティのDatastore Key ID |
file_type |
string |
✅ |
estimate_pdf / drawing / spec / other |
file_name |
string |
✅ |
ファイル名 |
gcs_uri |
string |
❌ |
gs://gobms/mitsumori-attachments/... |
file_size_bytes |
int |
❌ |
|
content_type |
string |
❌ |
MIME type |
uploaded_by |
string |
✅ |
google_account |
created_at |
datetime |
✅ |
|
8. 採番設計
Datastoreにはシーケンス機能がないため、アプリ層で採番する。
8.1 見積番号(quotation_no)
# フォーマット: MITQ-YYYYMMDD-NNNNNN
# カウンターはDatastoreで管理
COUNTER_KEY = ds_client.key("mitsumori_counters", "quotation_no")
def generate_quotation_no():
with ds_client.transaction():
counter = ds_client.get(COUNTER_KEY) or datastore.Entity(key=COUNTER_KEY)
n = counter.get("value", 0) + 1
counter["value"] = n
ds_client.put(counter)
today = datetime.now(JST).strftime("%Y%m%d")
return f"MITQ-{today}-{str(n).zfill(6)}"
9. Phase 2以降のKind(参考)
| Kind名 |
説明 |
Phase |
mitsumori_agreements |
契約 |
Phase 2 |
mitsumori_orders |
Marcury発注受領 |
Phase 3 |
mitsumori_rfq_imports |
Toriteki→Mitsumori見積連携ログ |
Phase 2 |
mitsumori_rfq_triggers |
Toriteki RFQ起票トリガーログ |
Phase 2 |
mitsumori_suppliers |
仕入先マスタ |
Phase 2 |
mitsumori_idempotency |
冪等性キャッシュ |
Phase 2 |
mitsumori_audit_logs |
監査ログ |
Phase 2 |
10. Open Questions(Phase 1着手前確認事項)
- [x] DB: Cloud Datastore確定(2026-03-14 PM決定)
- [x] Namespace:
mitsumori / mitsumori-staging / mitsumori-dev
- [ ] 見積番号プレフィックス:
MITQ- でよいか、Fredy既存番号との衝突回避が必要か
- [ ] 建物マスタとToriteki施設マスタのマッピング方針(Phase 1では独立、Phase 2でAPI連携)
- [ ] 工事確認の承認者設定: 部門マスタの
approval_email_construction で一律管理か、施設ごとに個別設定か
本文書はClaudeCodeが2026-03-14にPostgreSQL設計をDatastore設計に全面改訂。Fredy実機調査・機能要件定義・インフラ設計・Toriteki実装パターンを統合。