ken_nogi/Pleasanter/営業積算システム/docs/site-335410_335411_積算問合せシステム_spec.md
Kenichiro NOGI ce58cb4be4 初回コミット: dev配下(NodeSrv/Pleasanter等)をGitea管理下に統合
GitHub(nextgroup2706/ken_nogi)は今後使わず自社Gitea運用に切替え。
NodeSrvは旧リポジトリの履歴を破棄しファイルのみ統合(Dokploy用サービスアカウントは
別途mygit-admin/NodeSrv.gitに履歴あり)。notepmエクスポート(12GB)とPleasanter
インストーラzip(208MB)はサイズが大きいため.gitignoreで除外。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 15:37:06 +09:00

219 KiB
Raw Permalink Blame History

仕様書積算問合せシステムSiteId: 335410 / 335411

対象2サイト構成システム。AI学習用途、網羅性優先、可読性より情報欠落ゼロを優先する。

  • ①物件基本情報 Ver0.3SiteId: 335410 — 取得元: site-335410_latest.json
  • ②問い合わせ内容管理 Ver0.3SiteId: 335411 — 取得元: site-335411_latest.json
  • 取得日時(=両サイトの最終反映日時335410=2026-08-09T14:39:26 / 335411=2026-08-09T18:42:09いずれもJST、latest.json取得時点で最新の反映内容を含む
  • 設定バージョン:両サイトとも SiteSettings.Version: 1.017
  • 両サイトとも ParentId: 74663(同一親サイト配下の兄弟サイト)

0. システム概要

新築物件の「営業積算問い合わせ」業務を扱う2サイト構成システム。

  • ①物件基本情報335410:施主・物件の基本情報(施主名、エリア、契約ブランド、担当者等)を登録するマスター的サイト。登録完了後、この画面から「新規見積依頼」ボタンで②サイトへレコードを作成し依頼を開始する。
  • ②問い合わせ内容管理335411①から作成された個別の見積問い合わせ積算依頼を管理するサイト。依頼者⇔営業積算課Dept25間の質疑応答、見積回答、遅延連絡、積算発注課Dept26等への引継ぎまでを1レコードのライフサイクルとして扱う。

2サイトはClassZ②→①のLinks参照列で1:多①1件に対し②は複数の問い合わせ子番ClassBの関係にある。詳細は7章「他サイト連携」参照。

外部マスタとして以下のサイトを参照する(詳細後述):

  • SiteId 189112★マスターシート施主・物件属性のマスタ
  • SiteId 210699エリアマスタ
  • SiteId 109100契約ブランドマスタ
  • SiteId 96243契約営業所マスタ
  • SiteId 192267祝日テーブル営業日計算用
  • SiteId 335450遅延理由マスタ②の遅延連絡機能で参照

1. サイト基本情報

項目 ①物件基本情報(335410) ②問い合わせ内容管理(335411)
SiteId 335410 335411
TenantId 1 1
Title ①物件基本情報 Ver0.3 ②問い合わせ内容管理 Ver0.3
ReferenceType Results Results
ParentId 74663 74663
InheritPermission 335410自サイト権限は自サイト直接設定、継承なし 335411自サイト権限は自サイト直接設定、継承なし
Publish false false
DisableCrossSearch false false
Creator(UserId) 737 737
Updator(UserId) 995 995
CreatedTime 2026-03-10T13:41:04 2026-03-10T13:41:38
UpdatedTime 2026-08-09T14:39:26 2026-08-09T18:42:09
SiteSettings.Version 1.017 1.017
Bodyサイト説明 ・2026.3.10作成 ・2026.3.10作成

一覧・編集画面ガイド文GridGuideEditorGuide、両者同一内容

335410:

新規物件は、一覧画面の右上「+新規作成」ボタンから登録してください。
見積依頼は「新規見積依頼」ボタンから依頼を進めてください。

■LINEWORKS[営業積算相談窓口](https://line.worksmobile.com/message/send?userNoList=800300000001353&version=25&type=counselor) ※窓口の対応内容や対応時間の確認は[こちら](https://board.worksmobile.com/main/article/4080000000785782853?t=41276&boardNo=my&isShowFilesArea=false)
■LINEWORKS掲示板[営業積算関連情報ポータル](https://board.worksmobile.com/main/article/4080000000813091905?t=70969&boardNo=my&isShowFilesArea=false)

335411:

新規依頼は【①物件登録情報】の各施主ページ内から行ってください!
※②問い合わせ内容管理ページの「+新規作成」から依頼するとエラーが出ます

■LINEWORKS[営業積算相談窓口](https://line.worksmobile.com/message/send?userNoList=800300000001353&version=25&type=counselor) ※窓口の対応内容や対応時間の確認は[こちら](https://board.worksmobile.com/main/article/4080000000785782853?t=41276&boardNo=my&isShowFilesArea=false)
※不明点がある場合や期日前倒し希望の場合は、窓口までご連絡ください
※期日前倒し連絡がない場合は、対応できかねます
■LINEWORKS掲示板[営業積算関連情報ポータル](https://board.worksmobile.com/main/article/4080000000813091905?t=70969&boardNo=my&isShowFilesArea=false)

2. アクセス権限Permissions

両サイトともInheritPermissionが自サイトを指しており、権限は継承ではなく自サイト直接設定。権限値はビットフラグの加算方式のため、正確な意味は環境のロール定義511=フル権限相当、7/3等は限定権限に依存する。以下はresolve-names.jsによるマスターデータ名前解決結果。

335410①物件基本情報

種別 対象ID 対象名 PermissionType
Dept 25 営業積算課 511
Group 27 ネクストグループ全社員 3
User 460 野木 健一郎 511
User 678 高橋 光晴 511
User 995 システムユーザ 511

335411②問い合わせ内容管理

種別 対象ID 対象名 PermissionType
Dept 25 営業積算課 511
Dept 26 積算発注管理課 7
Group 27 ネクストグループ全社員 3
User 460 野木 健一郎 511
User 678 高橋 光晴 511
User 995 システムユーザ 511

335411のみ「積算発注管理課(Dept26)」がPermissionType 7限定権限で追加参加している点が335410との差分。これは③積算発注タブ(_Tab-3)の閲覧・入力対象部署と一致する3.2節・11章参照

3. 画面構成

3.1〜3.3 ①物件基本情報335410

一覧画面列(GridColumns) — Site 335410

列名(内部) 表示ラベル 種別
1 ResultId (標準ラベル) 自動採番ID
2 ClassA 管理番号 文字列(分類・1文字)
3 ClassS 案件状況 文字列(分類・1文字)
4 ClassB 登録者 文字列(分類・1文字)
5 Class001 施主名 文字列(拡張分類・3桁連番)
6 Class021 建上地 文字列(拡張分類・3桁連番)
7 Class022 エリア 文字列(拡張分類・3桁連番)
8 Class023 契約ブランド 文字列(拡張分類・3桁連番)
9 Class040 物件担当者 文字列(拡張分類・3桁連番)
10 UpdatedTime (標準ラベル) 更新日時(システム項目)

Class001のGridDesign[Class001][Class002]\n[Class003][Class004]施主1姓名カナを2行組版表示。Class040のGridDesign営業:[Class032] 設計:[Class034] \nIC[Class036] 監督:[Class037]項目自体はChoicesTextを持たず、一覧表示専用の合成列

タイトル表示 — Site 335410

  • TitleColumns: ClassA + Class022 + Class023
  • TitleSeparator:
  • 結合式: [ClassA/管理番号] ■ [Class022/エリア] ■ [Class023/契約ブランド]

検索・フィルタ設定 — Site 335410

  • FilterColumns一覧の絞り込み対象列: Status, ClassB, Class022, Class031〜037
  • LinkColumnsLinks参照時に他サイトから見える列: ResultId, Title, ClassA, ClassB, ClassK, ClassS, ClassY, ClassZ, Class021〜023, Class031, Class032

編集画面レイアウト — Site 335410

タブはGeneral物件情報1本のみGeneralTabLabelText: 物件情報TabLatestId: 1=追加タブなし)。

タブ: General
項目名(内部) ラベル名 種別/備考
1 ResultId ID(自動採番) 自動採番ID
2 Ver Ver(バージョン) バージョン(システム項目)
3 Title タイトル タイトル
4 Comments 変更履歴 コメント欄
5 ClassA 管理番号 文字列(分類・1文字)
6 ClassB 登録者 文字列(分類・1文字)
7 Status 登録状況 状態(Status) [Hide]
8 ClassF 文字列(分類・1文字) [Hide]
9 _Section-1 セクション開始「(無題セクション)」
10 ClassY マスターシート 文字列(分類・1文字)
11 ClassZ 契約コード 文字列(分類・1文字)
12 ClassS 案件状況 文字列(分類・1文字)
13 _Section-2 セクション開始「物件情報」
14 Class001 施主1・姓 文字列(拡張分類・3桁連番)
15 Class002 施主1・名 文字列(拡張分類・3桁連番)
16 Class003 施主1・姓カナ 文字列(拡張分類・3桁連番)
17 Class004 施主1・名カナ 文字列(拡張分類・3桁連番)
18 _Section-7 セクション開始「(無題セクション)」
19 Class005 施主2・姓 文字列(拡張分類・3桁連番)
20 Class006 施主2・名 文字列(拡張分類・3桁連番)
21 Class007 施主2・姓カナ 文字列(拡張分類・3桁連番)
22 Class008 施主2・名カナ 文字列(拡張分類・3桁連番)
23 _Section-8 セクション開始「(無題セクション)」
24 Class009 施主3・姓 文字列(拡張分類・3桁連番)
25 Class010 施主3・名 文字列(拡張分類・3桁連番)
26 Class011 施主3・姓カナ 文字列(拡張分類・3桁連番)
27 Class012 施主3・名カナ 文字列(拡張分類・3桁連番)
28 Class015 施主名マージ 文字列(拡張分類・3桁連番) [Hide]
29 _Section-9 セクション開始「(無題セクション)」
30 Class021 建上地 文字列(拡張分類・3桁連番)
31 Class022 エリア 文字列(拡張分類・3桁連番)
32 Class023 契約ブランド 文字列(拡張分類・3桁連番)
33 Class024 防火指定 文字列(拡張分類・3桁連番)
34 Class025 建物種類 文字列(拡張分類・3桁連番)
35 ClassM エリア記号 文字列(分類・1文字) [Hide]
36 ClassN ブランド記号 文字列(分類・1文字) [Hide]
37 _Section-4 セクション開始「スケジュール ※商談中の場合は、仮予定日を選択してください(後から変更可)」
38 Date001 承認図予定日 日付(拡張・3桁連番)
39 Date002 着工予定日 日付(拡張・3桁連番)
40 _Section-3 セクション開始「物件担当者」
41 Class031 契約営業所 文字列(拡張分類・3桁連番)
42 Class032 主担当営業 文字列(拡張分類・3桁連番)
43 Class033 副担当営業 文字列(拡張分類・3桁連番)
44 Class034 営業設計 文字列(拡張分類・3桁連番)
45 Class035 管理設計 文字列(拡張分類・3桁連番)
46 Class036 担当IC 文字列(拡張分類・3桁連番)
47 Class037 担当監督 文字列(拡張分類・3桁連番)
48 Class038 所属長 文字列(拡張分類・3桁連番)
49 Class151 担当者一覧 文字列(拡張分類・3桁連番) [Hide]
50 _Section-6 セクション開始「CCで送ってほしい社員・パートがいる場合は、名前を検索・選択してください」
51 Class061 CC① 文字列(拡張分類・3桁連番)
52 Class062 CC② 文字列(拡張分類・3桁連番)
53 Class063 CC③ 文字列(拡張分類・3桁連番)
54 Class064 CC④ 文字列(拡張分類・3桁連番)
55 Class152 CC一覧 文字列(拡張分類・3桁連番) [Hide]
56 Class153 担当者・CC一覧 文字列(拡張分類・3桁連番) [Hide]
57 Class154 依頼者・担当者・CC一覧 文字列(拡張分類・3桁連番) [Hide]
58 Class199 重複許容 文字列(拡張分類・3桁連番)
59 Class200 重複チェック 文字列(拡張分類・3桁連番) [Hide]
60 _Links-335411 他サイトLinksウィジェット表示(SiteId 335411)=この物件に紐づく②問い合わせ内容管理レコード一覧を編集画面下部に表示

Sections定義Id/ラベル/AllowExpand/Expand: Id1(無題,false,true) / Id2(物件情報,false,true) / Id7(無題,false,true) / Id8(無題,false,true) / Id9(無題,false,true) / Id4(スケジュール ※商談中の場合は、仮予定日を選択してください(後から変更可),false,true) / Id3(物件担当者,false,true) / Id6(CCで送ってほしい社員・パートがいる場合は、名前を検索・選択してください,false,true)。SectionLatestId: 9。全セクション折りたたみ不可AllowExpand:false

3.1〜3.3 ②問い合わせ内容管理335411

一覧画面列(GridColumns) — Site 335411

列名(内部) 表示ラベル 種別
1 ClassA 親番 文字列(分類・1文字)
2 ClassB 子番 文字列(分類・1文字)
3 Status (標準ラベル) 状態(Status)
4 ClassO 施主情報 文字列(分類・1文字)
5 CheckA 事前相談済み チェックボックス(1文字)
6 ClassE アップ希望日時 文字列(分類・1文字)
7 ClassC 担当者 文字列(分類・1文字)
8 DescriptionC 依頼内容 説明(複数行文字列・1文字)
9 Attachments031 依頼ファイル 添付ファイル(拡張・3桁連番)
10 Check071 質疑あり チェックボックス(拡張・3桁連番)
11 Check072 回答あり チェックボックス(拡張・3桁連番)
12 DescriptionF 見積回答 説明(複数行文字列・1文字)
13 Attachments061 (標準ラベル) 添付ファイル(拡張・3桁連番)
14 Comments (標準ラベル) コメント欄
15 UpdatedTime (標準ラベル) 更新日時(システム項目)

ClassOのGridDesign[Class001]\n[Class002]\n[Class004]施主名・建上地・ブランドを一覧セル内に合成表示、項目自体はGridLabelText「施主情報」のみで独自ChoicesTextなし。DescriptionFのGridDesign[Description091]\n[Description092]見積回答別途費用を合成表示。ClassEのGridDesign[DateA]\n[ClassE]アップ希望日時間を合成。UpdatedTimeのGridDesign[UpdatedTime]\n[Updator]。ClassCのGridDesign依頼者:[ClassC]\n営業担当[ClassD]GridLabelTextは「担当者」

タイトル表示 — Site 335411

  • TitleColumns: ClassA + ClassB + Class003 + Class004
  • TitleSeparator: -
  • 結合式: [ClassA/親番] - [ClassB/子番] - [Class003/エリア] - [Class004/ブランド]

検索・フィルタ設定 — Site 335411

  • FilterColumns: Status, Owner, Class003, Class004, Class011, Class062, Class064, Class101〜103, DateA, Date081
  • LinkColumns: ClassA, ClassB, ClassC, Class062, DateA, Class091, Description061

編集画面レイアウト — Site 335411

タブ構成:GeneralTabLabelText: 問い合わせ情報既定タブ独自タブ3本。TabLatestId: 4

EditorColumnHashキー タブ表示名 表示制御
General 問い合わせ情報 常時表示
_Tab-1Tabs配列Id:1 質疑回答 常時表示(タブ色は質疑応答ステータスに応じ3_質疑回答テーブル関連スクリプトが動的変更)
_Tab-2Tabs配列Id:2 営業積算 既定非表示。営業積算課(Dept25)のみ01_基本スクリプト.jsbodyshow-sekisan-tabクラス付与→CSS#EditorTabs li[aria-controls="FieldSetTab2"]等が表示化
_Tab-3Tabs配列Id:3 積算発注 既定非表示。Dept25/26/28/66/36所属者、またはUserId 913/987/1153のみbodyshow-sekisan-hachu-tabクラス付与で表示化
タブ: General(問い合わせ情報)
項目名(内部) ラベル名 種別/備考
1 ResultId ID(自動採番) 自動採番ID
2 Ver Ver(バージョン) バージョン(システム項目)
3 Title レコード名 タイトル
4 ClassZ テスト・リンク用 文字列(分類・1文字) [Hide]①335410へのLinks参照キー、実体は「①物件基本情報からのリンク元ID」用途
5 Comments 変更履歴 コメント欄
6 ClassA 親番 文字列(分類・1文字)
7 ClassB 子番 文字列(分類・1文字)
8 ClassX 連番処理 文字列(分類・1文字) [Hide][ClassA]-[nnn]自動採番、子番ClassBの算出元
9 ClassC 依頼者 文字列(分類・1文字)
10 ClassD 主担当営業 文字列(分類・1文字)
11 _Section-1 セクション開始「案件情報」(折りたたみ可)
12 DateA アップ希望日 日付(1文字)
13 DateJ 選択可最短日 日付(1文字)
14 ClassE アップ希望時間 文字列(分類・1文字)
15 CheckA 事前相談済み チェックボックス(1文字)
16 ClassF 事前相談者 文字列(分類・1文字)
17 ClassG 現在の進捗 文字列(分類・1文字)
18 ClassT 現在の進捗(控え) 文字列(分類・1文字) [Hide]
19 ClassH 現場の進捗 文字列(分類・1文字)
20 ClassI 必要な見積金額 文字列(分類・1文字)
21 DescriptionA 問い合わせ経緯 説明(複数行文字列・1文字)
22 DateB 承認図予定日 日付(1文字)
23 DateC 着工予定日 日付(1文字)
24 _Section-2 セクション開始「物件情報」
25 Class001 施主名 文字列(拡張分類・3桁連番)
26 Class002 建上地 文字列(拡張分類・3桁連番)
27 Class003 エリア 文字列(拡張分類・3桁連番)
28 Class004 ブランド 文字列(拡張分類・3桁連番)
29 Class005 建物種類 文字列(拡張分類・3桁連番)
30 Class006 防火指定 文字列(拡張分類・3桁連番)
31 _Section-3 セクション開始「問い合わせ内容 ※入力不備があった場合は差し戻します※」
32 Class007 問合せ種別 文字列(拡張分類・3桁連番)ラジオ、選択肢「マトリックス以外」「マトリックス」、初期未選択、必須。2026-08-09追加。下に赤字「※最初に問い合わせの種類を選択してください※」。選択に応じて33〜39行目(メーカー〜前回の管理番号)と42〜44行目(積算Ver.〜nexthouseシリーズ)の表示・必須・DescriptionCマージ対象を切替。10章スクリプト参照
33 Class031 メーカー 文字列(拡張分類・3桁連番)
34 Class032 商品名 文字列(拡張分類・3桁連番)
35 Class033 品番 文字列(拡張分類・3桁連番)
36 Class034 施工範囲・箇所 文字列(拡張分類・3桁連番)
37 Description031 依頼内容補足 説明(拡張・3桁連番)
38 Class045 標準仕様 文字列(拡張分類・3桁連番)
39 Class050 前回の管理番号 文字列(拡張分類・3桁連番)
40 _Section-17 セクション開始「(無題セクション)」
41 Class038 積算Ver. 文字列(拡張分類・3桁連番)
42 Class039 マトリックスサイズ 文字列(拡張分類・3桁連番)
43 Class040 nexthouseシリーズ 文字列(拡張分類・3桁連番)2026-08-09、下に赤字「※nexthouseの場合のみ選択してください※」選択解除ボタン追加。必須は種別=マトリックスかつブランド(Class004)=109101/268122のときのみ、10章スクリプト参照
44 DescriptionC 依頼内容 説明(複数行文字列・1文字) [Hide]問合せ種別により、マトリックス以外はClass031〜034,Description031,Class045,Class050の7項目、マトリックスはClass038〜040の3項目を自動マージ集約。一覧のDescriptionC列表示用。10章スクリプト参照
45 _Section-18 セクション開始「(無題セクション)」
46 Attachments031 依頼ファイル 添付ファイル(拡張・3桁連番)あらゆる状況で必須対象外、2026-08-09確定
47 Class052 回答希望送付先 文字列(拡張分類・3桁連番)あらゆる状況で必須対象外、2026-08-09確定
48 _Section-6 セクション開始「営業積算 ※※読み取り専用※※」
49 Date061 受付日 日付(拡張・3桁連番)
50 Class061 受付者 文字列(拡張分類・3桁連番)
51 Class062 状況 文字列(拡張分類・3桁連番)Status列と相互同期。11章参照
52 Date062 見積依頼日 日付(拡張・3桁連番)
53 Class063 見積依頼先 文字列(拡張分類・3桁連番)
54 _Section-7 セクション開始「見積回答結果」
55 Date063 見積受信日 日付(拡張・3桁連番)
56 Class065 見積種別 文字列(拡張分類・3桁連番)
57 Description061 見積結果 説明(拡張・3桁連番)
58 Description062 見積回答ファイル 説明(拡張・3桁連番)
59 _Links-335410 他サイトLinksウィジェット表示(SiteId 335410)紐づく①物件基本情報レコードへのリンク表示。ClassZのChoicesTextにNotReturnParentRecordオプション設定済み2026-08-09、①からの新規依頼作成後、遷移元に戻らず作成した②自身のレコードに留まる。12章参照
60 Class153 担当者・CC一覧 文字列(拡張分類・3桁連番) [Hide]
61 ClassY 依頼者・担当者・CC一覧 文字列(分類・1文字) [Hide]
タブ: _Tab-2営業積算、Dept25限定表示
項目名(内部) ラベル名 種別/備考
1 _Section-11 セクション開始「受付処理」
2 Date081 受付日 日付(拡張・3桁連番)(プロセス「受付」実行時、$p.set()で本日日付を設定。旧「データの変更」から移設、2026-08-09。受付者欄の右横に独自配置「受付」ボタン
3 Class081 受付者 文字列(拡張分類・3桁連番)2026-08-09、Usersテーブル検索型に変更。DeptId=25限定・UserIdソート。プロセス「受付」実行時にログインユーザーを設定
4 Date082 見積依頼日 日付(拡張・3桁連番)(プロセス「外部見積待ちへ」実行時、$p.set()で本日日付を設定。旧「データの変更」から移設、2026-08-09
5 Class082 見積依頼者 文字列(拡張分類・3桁連番)2026-08-09、Usersテーブル検索型に変更。DeptId=25限定・UserIdソート。プロセス「外部見積待ちへ」実行時にログインユーザーを設定。見積依頼者欄の右横に独自配置「外部見積待ちへ」ボタン
6 Class083 見積依頼先 文字列(拡張分類・3桁連番)
7 Class084 見積備考 文字列(拡張分類・3桁連番)
8 Description051 受付処理備考 説明(拡張・3桁連番)2026-08-09追加。受付処理セクション末尾
9 Status (標準ラベル) 状態(Status)本体14種類の選択肢。8.節参照。右横に「社内確認中へ」「問い合わせ不要」「差戻し」を独自配置ボタンとして一列配置2026-08-09
10 Class080 問い合わせジャンル 文字列(拡張分類・3桁連番)
11 _Section-12 セクション開始「遅延連絡」
12 Class085 遅延送付先 文字列(拡張分類・3桁連番)
13 Class086 遅延理由 文字列(拡張分類・3桁連番)SiteId 335450参照Lookup
14 Date084 遅延連絡日 日付(拡張・3桁連番)(プロセス「遅延連絡」実行時、$p.set()で本日日付を設定。旧「データの変更」から移設、2026-08-09
15 Class087 遅延連絡者 文字列(拡張分類・3桁連番)2026-08-09、Usersテーブル検索型に変更。DeptId=25限定・UserIdソート。プロセス「遅延連絡」実行時にログインユーザーを設定
16 Date085 返信予定日 日付(拡張・3桁連番)
17 Description081 遅延連絡内容 説明(拡張・3桁連番)
18 Description082 遅延連絡内容(控え) 説明(拡張・3桁連番) [Hide]
19 Description083 遅延連絡ログ 説明(拡張・3桁連番)
20 _Section-13 セクション開始「算出回答結果」
21 Date090 見積受信日 日付(拡張・3桁連番)
22 Class091 見積種別 文字列(拡張分類・3桁連番)
23 Description091 見積回答 説明(拡張・3桁連番)
24 Description092 別途費用・伝達事項 説明(拡張・3桁連番)
25 Description093 (標準ラベル) 説明(拡張・3桁連番) [Hide扱い相当。EditorColumnHash非掲載・091+092の自動マージ先Description061転記元]
26 Attachments101 見積回答ファイル 添付ファイル(拡張・3桁連番)対応する一括DLボタンestimateAttachmentsDLは、1件も添付が無い場合は非表示。2026-08-09
27 Num101 算出回答回数 数値(拡張・3桁連番)
28 Class101 算出者 文字列(拡張分類・3桁連番)2026-08-09、Usersテーブル検索型に変更。DeptId=25限定・UserIdソート
29 Class102 チェック者 文字列(拡張分類・3桁連番)2026-08-09、Usersテーブル検索型に変更。DeptId=25限定・UserIdソート
30 Date103 返信日 日付(拡張・3桁連番)(プロセス「一部回答」「見積回答」実行時、$p.set()で本日日付を設定。旧「データの変更」から移設、2026-08-09
31 Class103 返信者 文字列(拡張分類・3桁連番)2026-08-09、Usersテーブル検索型に変更。DeptId=25限定・UserIdソート。プロセス「一部回答」「見積回答」実行時にログインユーザーを設定。返信者欄の右横に「一部回答」上段「見積回答」下段を独自配置ボタンとして縦並び配置
32 Class095 回答送付先 文字列(拡張分類・3桁連番)「一部回答」「見積回答」通知メールのCC先[Class095]として使用中のため存置、2026-08-09使用箇所確認
33 Description101 見積回答ログ 説明(拡張・3桁連番)

2026-08-09時点でDate101「算出日」・Date102「チェック日」・Description095「回答送付先一覧」を削除。前2者は依頼になかったため、後者はスクリプト・通知メールいずれからも参照が無いことを確認した上で削除。詳細は12章参照

Description093はEditorColumnHash配列に含まれないが、Columns定義には存在しないため実質未使用列の可能性あり。詳細は12章「気になる点」参照

タブ: _Tab-1(質疑回答、常時表示)
項目名(内部) ラベル名 種別/備考
1 Class071 質疑回答ステータス 文字列(拡張分類・3桁連番) [Hide]0=質疑なし/1=質疑あり/2=回答あり/3=回答確認済み)
2 Description071 質疑回答データ保存用 説明(拡張・3桁連番) [Hide]質疑応答メッセージ履歴のJSON配列を保管
3 Class075 質疑回答送信者 文字列(拡張分類・3桁連番) [Hide]
4 Date071 質疑回答送信日時 日付(拡張・3桁連番) [Hide]
5 _Section-8 セクション開始「質疑回答ログ」(折りたたみ可)
6 _Section-9 セクション開始「営業積算課からの質疑」
7 Description075 質疑内容 説明(拡張・3桁連番)
8 Attachments071 質疑ファイル 添付ファイル(拡張・3桁連番)
9 Date073 質疑回答期日 日付(拡張・3桁連番)送信時に土日祝を除く5営業日後を自動計算
10 Class077 質疑送付先 文字列(拡張分類・3桁連番)
11 Check071 質疑あり チェックボックス(拡張・3桁連番)
12 Num071 質疑回数 数値(拡張・3桁連番)
13 _Section-10 セクション開始「依頼者からの回答」
14 Description076 回答内容 説明(拡張・3桁連番)
15 Attachments072 回答ファイル 添付ファイル(拡張・3桁連番)
16 Check072 回答あり チェックボックス(拡張・3桁連番)
17 Num072 回答回数 数値(拡張・3桁連番)
18 Date074 回答確認日 日付(拡張・3桁連番)(営業積算課の確認操作時に設定)
タブ: _Tab-3積算発注、Dept25/26/28/66/36またはUserId 913/987/1153限定表示
項目名(内部) ラベル名 種別/備考
1 _Section-14 セクション開始「営業積算課からの引継ぎ」
2 Description111 引継ぎ備考 説明(拡張・3桁連番)
3 Attachments111 NET見積原本 添付ファイル(拡張・3桁連番)
4 Attachments112 算出根拠資料 添付ファイル(拡張・3桁連番)
5 Class111 採用書類 文字列(拡張分類・3桁連番)
6 Attachments113 その他 添付ファイル(拡張・3桁連番)
7 _Section-15 セクション開始「積算発注課管理」
8 Date111 データ出力日 日付(拡張・3桁連番)

Sections定義一覧Id/ラベル/AllowExpand1(案件情報,true) / 2(物件情報,false) / 3(問い合わせ内容 ※入力不備があった場合は差し戻します※,false) / 17(無題,false) / 18(無題,false) / 6(営業積算 ※※読み取り専用※※,false) / 7(見積回答結果,false) / 11(受付処理,false) / 12(遅延連絡,false) / 13(算出回答結果,false) / 8(質疑回答ログ,true) / 9(営業積算課からの質疑,false) / 10(依頼者からの回答,false) / 14(営業積算課からの引継ぎ,false) / 15(積算発注課管理,false)。SectionLatestId: 18。全セクションExpand:true初期展開済み

4. 項目定義Columns全項目

種別はColumnNameプレフィックスからの推定。ChoicesTextが[[Users*]]はユーザー一覧選択、[[数字]]は単純Link参照、JSON配列/オブジェクトはLookup設定詳細は7章

4.1 ①物件基本情報335410— 全50項目

列名(内部) 種別 ラベル GridLabel 主要設定 備考
Comments コメント欄 変更履歴 画像貼付不可
Status 状態(Status) 登録状況 Hide, CSS:code-readonly2 選択肢: 100,新規登録,新規,new-status-white / 200,登録完了,登録,new-status-pink / 900,依頼終了,依頼,new-status-blue / 910,保留,保留,new-status-grey / 920,取消,取消,new-status-grey
ClassA 文字列(分類・1文字) 管理番号 NoWrap, CSS:code-readonly2, 自動採番:[yy][nnnn][ClassM][ClassN] 補足HTML付き(4.3節参照)
ClassB 文字列(分類・1文字) 登録者 検索窓あり, CSS:code-readonly2, 初期値:Self ユーザー一覧選択
ClassF 文字列(分類・1文字) Hide
ClassZ 文字列(分類・1文字) 契約コード コピー時デフォルト保持, CSS:code-readonly2 ClassY選択時にマスターシート(189112)ClassAから自動転記
ClassS 文字列(分類・1文字) 案件状況 NoWrap, 空欄なし, 初期値:0 選択肢: 0,未契約,未契約,new-status-light-white / 1,契約済み,契約済み,new-status-greenClassY/ClassZの値からスクリプト自動判定、10章参照
Class001 文字列(拡張分類・3桁連番) 施主1・姓 施主名 NoWrap, GridDesign:[Class001][Class002]\n[Class003][Class004]
Class002 文字列(拡張分類・3桁連番) 施主1・名
Class003 文字列(拡張分類・3桁連番) 施主1・姓カナ NoWrap, 必須, 正規表現検証あり(^[ァ-ヶー0-0-9  ]+$) 入力ガイド:全角カタカナで入力
Class004 文字列(拡張分類・3桁連番) 施主1・名カナ 必須, 正規表現検証あり(^[ァ-ヶー0-0-9  ]+$) 入力ガイド:全角カタカナで入力
Class005 文字列(拡張分類・3桁連番) 施主2・姓 NoWrap
Class006 文字列(拡張分類・3桁連番) 施主2・名
Class007 文字列(拡張分類・3桁連番) 施主2・姓カナ NoWrap, 正規表現検証あり
Class008 文字列(拡張分類・3桁連番) 施主2・名カナ 正規表現検証あり
Class009 文字列(拡張分類・3桁連番) 施主3・姓 NoWrap
Class010 文字列(拡張分類・3桁連番) 施主3・名
Class011 文字列(拡張分類・3桁連番) 施主3・姓カナ NoWrap, 正規表現検証あり
Class012 文字列(拡張分類・3桁連番) 施主3・名カナ 正規表現検証あり
ClassM 文字列(分類・1文字) エリア記号 Hide, NoWrap Class022(エリア)選択時にServerScript事前取得マスタから逆引き転記
ClassN 文字列(分類・1文字) ブランド記号 Hide Class023(契約ブランド)選択時にServerScript事前取得マスタから逆引き転記
Date001 日付(拡張・3桁連番) 承認図予定日
Date002 日付(拡張・3桁連番) 着工予定日
Class199 文字列(拡張分類・3桁連番) 重複許容 選択肢: 重複1〜重複10営業積算専用の重複チェック回避弁、CreateColumnAccessControls/ReadColumnAccessControls/UpdateColumnAccessControlsでDept25限定、5章参照
Class200 文字列(拡張分類・3桁連番) 重複チェック Hide, 重複チェック対象(NoDuplication) 重複時メッセージ:本物件はすでに登録されております。同姓同名の別物件の場合は営業積算窓口へご連絡ください(施主カナ+エリア+重複許容の連結値、10章スクリプト参照
UpdatedTime 更新日時(システム項目) GridFormat:Ymdhm
Class061 文字列(拡張分類・3桁連番) CC① 検索窓あり ユーザー一覧選択
Class062 文字列(拡張分類・3桁連番) CC② 検索窓あり ユーザー一覧選択
Class063 文字列(拡張分類・3桁連番) CC③ NoWrap, 検索窓あり ユーザー一覧選択
Class064 文字列(拡張分類・3桁連番) CC④ 検索窓あり ユーザー一覧選択
Class031 文字列(拡張分類・3桁連番) 契約営業所 JSON形式Lookup設定あり(SiteId 96243、7章参照)
Class032 文字列(拡張分類・3桁連番) 主担当営業 NoWrap, 検索窓あり ユーザー一覧選択
Class033 文字列(拡張分類・3桁連番) 副担当営業 検索窓あり ユーザー一覧選択
Class034 文字列(拡張分類・3桁連番) 営業設計 NoWrap, 検索窓あり ユーザー一覧選択
Class035 文字列(拡張分類・3桁連番) 管理設計 検索窓あり ユーザー一覧選択
Class036 文字列(拡張分類・3桁連番) 担当IC NoWrap, 検索窓あり ユーザー一覧選択
Class037 文字列(拡張分類・3桁連番) 担当監督 検索窓あり ユーザー一覧選択
Class038 文字列(拡張分類・3桁連番) 所属長 NoWrap, 検索窓あり ユーザー一覧選択(Class031選択時にServerScript事前取得マスタから逆引き転記)
Class022 文字列(拡張分類・3桁連番) エリア 必須 JSON形式Lookup設定あり(SiteId 210699、7章参照)
Class023 文字列(拡張分類・3桁連番) 契約ブランド NoWrap JSON形式Lookup設定あり(SiteId 109100、7章参照)
Class024 文字列(拡張分類・3桁連番) 防火指定 NoWrap 選択肢: 無,無,無,status-grey / 準防火,準防火,準防,status-yellow / 準耐火,準耐火,準耐,status-red
Class025 文字列(拡張分類・3桁連番) 建物種類 選択肢: 平屋 / 2階建て / 3階建て
Class021 文字列(拡張分類・3桁連番) 建上地 CSS:field-wide
Class040 文字列(拡張分類・3桁連番) 物件担当者 GridDesign専用列(4.2節参照) ChoicesText/LabelTextなし。一覧表示専用の合成列
ClassY 文字列(分類・1文字) マスターシート NoWrap, 検索窓あり, コピー時デフォルト保持, CSS:field-wide JSON形式Lookup設定あり(SiteId 189112、SearchFormat:「[ClassA] - [ClassQ] 様 ([ClassF] [ClassG])」、7章参照)。補足HTML付き(4.3節参照)
Class015 文字列(拡張分類・3桁連番) 施主名マージ Hide, NoWrap, CSS:field-wide 施主1姓名+カナのマージ文字列(スクリプト自動生成)
Class151 文字列(拡張分類・3桁連番) 担当者一覧 Hide, 検索窓あり, 複数選択, CSS:field-wide ユーザー一覧選択(主担当営業〜所属長の重複除去マージ、スクリプト自動生成)
Class152 文字列(拡張分類・3桁連番) CC一覧 Hide, 検索窓あり, 複数選択, CSS:field-wide ユーザー一覧選択(CC①〜④の重複除去マージ、スクリプト自動生成)
Class153 文字列(拡張分類・3桁連番) 担当者・CC一覧 Hide, 検索窓あり, 複数選択, CSS:field-wide ユーザー一覧選択(Class151+Class152の重複除去マージ、スクリプト自動生成)
Class154 文字列(拡張分類・3桁連番) 依頼者・担当者・CC一覧 Hide, 検索窓あり, 複数選択, CSS:field-wide ユーザー一覧選択(登録者ClassB+Class153の重複除去マージ、スクリプト自動生成)

4.2 ②問い合わせ内容管理335411— 全107項目2026-08-09時点。Class007・Description051追加、Date101・Date102・Description095削除により108→107

列名(内部) 種別 ラベル GridLabel 主要設定 備考
ClassA 文字列(分類・1文字) 親番 NoWrap, コピー時デフォルト保持, 空欄なし, CSS:code-readonly2
Owner ユーザー選択(Owner) NoWrap 標準Owner項目、ChoicesText空・DefaultInput空
ClassD 文字列(分類・1文字) 主担当営業 検索窓あり, CSS:code-readonly2 ユーザー一覧選択①ClassZ経由の自動転記先
ClassE 文字列(分類・1文字) アップ希望時間 アップ希望日時 NoWrap, 空欄なし, 初期値:指定なし, GridDesign:[DateA]\n[ClassE] 選択肢: 指定なし / 9:00〜18:0030分刻み
ClassF 文字列(分類・1文字) 事前相談者 選択肢: 営業積算相談窓口 / 小竹 忍 / 菅原 千夏 / 大内 詩織。説明・入力ガイド:事前相談した担当を選択してください
ClassG 文字列(分類・1文字) 現在の進捗 NoWrap, 表示形式:Radio 選択肢: 0,商談~承認図 / 1,承認図後・着工中(監督確認済/お客様請求可)値によりClassH/ClassI/DescriptionAの表示切替、10章参照
ClassT 文字列(分類・1文字) 現在の進捗(控え) Hide ClassGの常時ミラーClassG読取専用化後も表示判定に使うための控え
ClassH 文字列(分類・1文字) 現場の進捗 NoWrap, 表示形式:Radio 選択肢: 0,承認図後~着工前 / 1,着工中 / 2,引渡し前
ClassI 文字列(分類・1文字) 必要な見積金額 NoWrap, 表示形式:Radio 選択肢: 0,承認図時まで / 1,承認図以降
DateA 日付(1文字) アップ希望日 入力ガイド:土日祝日を除く5営業日以降DateJ選択可最短日との比較チェックあり
CheckA チェックボックス(1文字) 事前相談済み NoWrap
DescriptionA 説明(複数行文字列・1文字) 問い合わせ経緯 画像貼付不可 入力ガイド:入力例承認図時に決定していた商品が廃盤となり、商品の再選定が必要になったため。補足HTML付き(4.3節参照)
DateB 日付(1文字) 承認図予定日 CSS:code-readonly2 ①Date001からの自動転記(readonly)
DateC 日付(1文字) 着工予定日 CSS:code-readonly2 ①Date002からの自動転記(readonly)
Class001 文字列(拡張分類・3桁連番) 施主名 CSS:code-readonly2, CSS:field-wide ①Class015(施主名マージ)からの自動転記(readonly)
Class002 文字列(拡張分類・3桁連番) 建上地 CSS:code-readonly2, CSS:field-wide ①Class021からの自動転記(readonly)
Class003 文字列(拡張分類・3桁連番) エリア CSS:code-readonly2, Linkフラグ(参照専用表示) SiteId 210699 参照(単純Link、①Class022からの自動転記readonly)
Class004 文字列(拡張分類・3桁連番) ブランド CSS:code-readonly2, Linkフラグ(参照専用表示) SiteId 109100 参照(単純Link、①Class023からの自動転記readonly)
Class005 文字列(拡張分類・3桁連番) 建物種類 NoWrap, CSS:code-readonly2 選択肢: 平屋 / 2階建て / 3階建て①Class025からの自動転記readonly
Class006 文字列(拡張分類・3桁連番) 防火指定 CSS:code-readonly2 選択肢: 無 / 準防火 / 準耐火①Class024からの自動転記readonly
Status 状態(Status) NoWrap 選択肢14種8章参照。Class062と相互同期(10章スクリプト)
Class031 文字列(拡張分類・3桁連番) メーカー CSS:field-wide 入力ガイド:正式名称で入力してくださいDAIKEN
Class038 文字列(拡張分類・3桁連番) 積算Ver. NoWrap 入力ガイド:入力例260211-AC-全-002。補足HTML付き(4.3節参照)
Class040 文字列(拡張分類・3桁連番) nexthouseシリーズ NoWrap, 空欄なし, 表示形式:Radio 選択肢: LAVIE / FAMILIE・PRIME / CERAGRAN。必須は問合せ種別(Class007)=マトリックスかつブランド(Class004)=109101(nexthouse)/268122(nexthouse VALUE)のときのみ10章スクリプト。補足HTML付き2件赤字説明選択解除ボタン(4.3節参照、2026-08-09追加)
Class007 文字列(拡張分類・3桁連番) 問合せ種別 空欄なし, 表示形式:Radio, 選択肢2つ 選択肢: マトリックス以外 / マトリックス。初期未選択。2026-08-09追加。選択に応じてマトリックス以外群(Class031〜034,Description031,Class045,Class050)/マトリックス群(Class038〜040)の表示・必須・DescriptionCマージ対象を切替、保存時に非選択側はクリア(10章スクリプト)。補足HTML付き(4.3節参照)
Class045 文字列(拡張分類・3桁連番) 標準仕様 CSS:field-wide 説明:標準仕様からの変更差額を希望の場合は、該当の仕様をこちらへ入力してください。補足HTML付き(4.3節参照)
Class050 文字列(拡張分類・3桁連番) 前回の管理番号 CSS:field-wide 説明:既出問い合わせからの変更の場合は、該当の番号を選択してください
Class052 文字列(拡張分類・3桁連番) 回答希望送付先 検索窓あり, 複数選択, CSS:field-wide JSON形式Lookup設定あり(Usersテーブル、Class153絞込。7章参照)。補足HTML付き(4.3節参照)
Class061 文字列(拡張分類・3桁連番) 受付者 CSS:code-readonly2 Usersテーブル検索型(DeptId=25限定・UserIdソート、2026-08-09変更)。Tab2 Class081から自動転記readonly転記時UserId→氏名解決、10章スクリプト
Date061 日付(拡張・3桁連番) 受付日 CSS:code-readonly2 Tab2 Date081から自動転記readonly
Date062 日付(拡張・3桁連番) 見積依頼日 NoWrap, CSS:code-readonly2 Tab2 Date082から自動転記readonly
Class062 文字列(拡張分類・3桁連番) 状況 NoWrap, CSS:code-readonly2 選択肢14種Statusと同一・相互同期
Class063 文字列(拡張分類・3桁連番) 見積依頼先 CSS:code-readonly2, CSS:field-wide Tab2 Class083から自動転記readonly
Date063 日付(拡張・3桁連番) 見積受信日 CSS:code-readonly2 Tab2 Date090から自動転記readonly
Class064 文字列(拡張分類・3桁連番) 採用状況 Hide, NoWrap, 表示形式:Radio 選択肢: 採用 / 不採用 / 打合せ中未使用の可能性、12章参照
Class065 文字列(拡張分類・3桁連番) 見積種別 NoWrap, 表示形式:Radio, CSS:code-readonly2 選択肢: 概算 / 正式Tab2 Class091から自動転記readonly
Description061 説明(拡張・3桁連番) 見積結果 画像貼付不可, CSS:code-readonly2 Description091+092のマージ結果を転記(readonly、10章スクリプト)
Description062 説明(拡張・3桁連番) 見積回答ファイル 画像貼付不可, CSS:code-readonly2 見積回答プロセス実行時にAttachments101から一覧DLリンク生成(10章スクリプト)
Date071 日付(拡張・3桁連番) 質疑回答送信日時 Hide 現状未使用12章参照
Date073 日付(拡張・3桁連番) 質疑回答期日 CSS:code-readonly2 質疑送信時に土日祝除く5営業日後を自動計算
Date074 日付(拡張・3桁連番) 回答確認日 NoWrap, CSS:code-readonly2 営業積算確認操作時に本日日付を自動設定
Description071 説明(拡張・3桁連番) 質疑回答データ保存用 Hide, 画像貼付不可 質疑応答メッセージ履歴JSON配列の実体
Description075 説明(拡張・3桁連番) 質疑内容 画像貼付不可 送信すると自動クリアされる入力欄
Description076 説明(拡張・3桁連番) 回答内容 画像貼付不可 送信すると自動クリアされる入力欄
Attachments071 添付ファイル(拡張・3桁連番) 質疑ファイル
Attachments072 添付ファイル(拡張・3桁連番) 回答ファイル
Class071 文字列(拡張分類・3桁連番) 質疑回答ステータス Hide, 初期値:0 選択肢: 0,質疑なし,質疑なし,new-status-white / 1,質疑あり,質疑あり,new-status-red / 2,回答あり,回答あり,new-status-yellow / 3,回答確認済み,回答確認済み,new-status-green
Class075 文字列(拡張分類・3桁連番) 質疑回答送信者 Hide 質疑・回答送信のたびに送信者氏名で上書き
Date081 日付(拡張・3桁連番) 受付日 Tab2入力欄(→Date061へ転記)
Date082 日付(拡張・3桁連番) 見積依頼日 NoWrap Tab2入力欄(→Date062へ転記)
Date085 日付(拡張・3桁連番) 返信予定日 NoWrap 変更時にDescription081内の%返信予定日%プレースホルダー/旧値を置換(10章スクリプト)
Date090 日付(拡張・3桁連番) 見積受信日 Tab2入力欄(→Date063へ転記)
Date103 日付(拡張・3桁連番) 返信日 NoWrap プロセス「一部回答」「見積回答」実行時、$p.setで本日日付を設定旧「データの変更」から移設、2026-08-09
Class081 文字列(拡張分類・3桁連番) 受付者 Usersテーブル検索型(DeptId=25限定・UserIdソート、2026-08-09変更)。プロセス「受付」実行時にログインユーザーを設定
Class082 文字列(拡張分類・3桁連番) 見積依頼者 Usersテーブル検索型(DeptId=25限定・UserIdソート、2026-08-09変更)。プロセス「外部見積待ちへ」実行時にログインユーザーを設定
Class083 文字列(拡張分類・3桁連番) 見積依頼先 CSS:field-wide
Class084 文字列(拡張分類・3桁連番) 見積備考 CSS:field-wide
Description051 説明(拡張・3桁連番) 受付処理備考 2026-08-09追加。受付処理セクション(Tab-2)末尾、Class084の直後・Status手前
Class085 文字列(拡張分類・3桁連番) 遅延送付先 検索窓あり, 複数選択, CSS:field-wide JSON形式Lookup設定あり(Usersテーブル、Class153絞込。7章参照)
Class086 文字列(拡張分類・3桁連番) 遅延理由 AutoPostBack JSON形式Lookup設定あり(SiteId 335450、Description081へ自動返送。7章参照)
Class087 文字列(拡張分類・3桁連番) 遅延連絡者 Usersテーブル検索型(DeptId=25限定・UserIdソート、2026-08-09変更)。プロセス「遅延連絡」実行時にログインユーザーを設定
Description081 説明(拡張・3桁連番) 遅延連絡内容 画像貼付不可 遅延理由マスタからの雛形文+%返信予定日%プレースホルダー置換
Description083 説明(拡張・3桁連番) 遅延連絡ログ 遅延連絡送信のたびに先頭追記(10章スクリプト)
Description091 説明(拡張・3桁連番) 見積回答 画像貼付不可
Description092 説明(拡張・3桁連番) 別途費用・伝達事項 画像貼付不可
Class101 文字列(拡張分類・3桁連番) 算出者 Usersテーブル検索型(DeptId=25限定・UserIdソート、2026-08-09変更)
Class102 文字列(拡張分類・3桁連番) チェック者 Usersテーブル検索型(DeptId=25限定・UserIdソート、2026-08-09変更)
Class103 文字列(拡張分類・3桁連番) 返信者 Usersテーブル検索型(DeptId=25限定・UserIdソート、2026-08-09変更)。プロセス「一部回答」「見積回答」実行時にログインユーザーを設定。返信者欄の右横に一部回答(上段)・見積回答(下段)ボタンを縦並び配置
Description101 説明(拡張・3桁連番) 見積回答ログ 画像貼付不可 算出対応ログの累積保存先(10章スクリプト)
Attachments101 添付ファイル(拡張・3桁連番) 見積回答ファイル 見積回答プロセス実行時にDescription062へ一覧転記
Attachments111 添付ファイル(拡張・3桁連番) NET見積原本 Tab3、CreateColumnAccessControls/UpdateColumnAccessControlsでDept25/26限定(5章)
Attachments112 添付ファイル(拡張・3桁連番) 算出根拠資料 Tab3、Dept25/26限定
Class111 文字列(拡張分類・3桁連番) 採用書類 CSS:field-wide Tab3、Dept25/26限定
Attachments113 添付ファイル(拡張・3桁連番) その他 Tab3、Dept25/26限定
Description111 説明(拡張・3桁連番) 引継ぎ備考 画像貼付不可 Tab3、Dept25/26限定
Num071 数値(拡張・3桁連番) 質疑回数
Num072 数値(拡張・3桁連番) 回答回数
Num101 数値(拡張・3桁連番) 算出回答回数 NoWrap
Date064 日付(拡張・3桁連番) 使用期限 Hide 補足HTML付き(4.3節参照、現状未使用の可能性12章参照)
Attachments031 添付ファイル(拡張・3桁連番) 依頼ファイル
Date111 日付(拡張・3桁連番) データ出力日 Tab3、Dept25/26限定
ClassC 文字列(分類・1文字) 依頼者 担当者 NoWrap, CSS:code-readonly2, 初期値:Self, GridDesign:依頼者:[ClassC]\n営業担当[ClassD] ユーザー一覧選択。多くのProcess View条件(ClassC:["Own"])で本人判定に使用
Class091 文字列(拡張分類・3桁連番) 見積種別 NoWrap, 表示形式:Radio 選択肢: 概算 / 正式Tab2入力欄→Class065へ転記
Date091 日付(拡張・3桁連番) 使用期限 補足HTML付き(4.3節参照、EditorColumnHash非掲載現状未表示の可能性12章参照)
Class095 文字列(拡張分類・3桁連番) 回答送付先 複数選択, CSS:field-wide JSON形式Lookup設定あり(Usersテーブル、Class153絞込)。「一部回答」「見積回答」通知メールのCC先[Class095]として使用中と確認し存置2026-08-09、Description095は使用箇所なしのため削除
Class077 文字列(拡張分類・3桁連番) 質疑送付先 検索窓あり, 複数選択, CSS:field-wide JSON形式Lookup設定あり(Usersテーブル、Class153絞込)
UpdatedTime 更新日時(システム項目) GridDesign:[UpdatedTime]\n[Updator]
ClassO 文字列(分類・1文字) 施主情報 GridDesign専用列(4.2節参照) GridDesign:[Class001]\n[Class002]\n[Class004]。ChoicesText/LabelTextなし
DescriptionC 説明(複数行文字列・1文字) 依頼内容 Hide 説明:依頼内容マージ。Class031〜034,Description031,Class038〜040の自動集約先(10章スクリプト)
DescriptionF 説明(複数行文字列・1文字) 見積回答 GridDesign専用列(4.2節参照) GridDesign:[Description091]\n[Description092]。ChoicesText/LabelTextなし
Class032 文字列(拡張分類・3桁連番) 商品名 CSS:field-wide 入力ガイド:正しい商品名を入力してください(例)イエリア 格子間仕切
Class033 文字列(拡張分類・3桁連番) 品番 CSS:field-wide 入力ガイド:正しい品番を入力してくださいQW404-■■
Description031 説明(拡張・3桁連番) 依頼内容補足 画像貼付不可 説明:メーカー・商品名・品番以外に補足がある場合は、こちらに詳細を入力してください。
Class080 文字列(拡張分類・3桁連番) 問い合わせジャンル NoWrap, 空欄なし 選択肢: A.基本〜I.造作家具・建具・洗面 9択案内文
Title タイトル レコード名
Class039 文字列(拡張分類・3桁連番) マトリックスサイズ NoWrap, 表示形式:Radio 入力ガイド:入力例7×13
ClassZ 文字列(分類・1文字) テスト・リンク用 Hide, AutoPostBack, CSS:field-wide JSON形式Lookup設定あり(SiteId 335410、15項目転記。7章参照)。実質「①物件基本情報からのリンク元ID」として機能ラベル名と実際の用途が乖離、12章参照。ChoicesTextにNotReturnParentRecord:true追加2026-08-09。Pleasanter標準機能、①からの新規依頼作成後に遷移元へ戻らず②自身のレコードに留まる
Class034 文字列(拡張分類・3桁連番) 施工範囲・箇所 CSS:field-wide 説明:施工場所を入力してください ※複数パターンある場合は『A●●、B■■』のようにわかるように記載してください
Date084 日付(拡張・3桁連番) 遅延連絡日 NoWrap
DateJ 日付(1文字) 選択可最短日 CSS:code-readonly2 サーバースクリプト取得の祝日データからクライアント側で自動算出(readonly)
ClassB 文字列(分類・1文字) 子番 CSS:code-readonly2 ClassXから算出、クライアント・サーバー両方に同一ロジックで自動保存(10章)
ClassX 文字列(分類・1文字) 連番処理 Hide, NoWrap, 自動採番:[ClassA]-[nnn] ClassBの算出元
Description082 説明(拡張・3桁連番) 遅延連絡内容(控え) Hide Date085置換処理のキー保持用前回置換に使った返信予定日を記憶
Check071 チェックボックス(拡張・3桁連番) 質疑あり NoWrap, CSS:code-readonly2
Check072 チェックボックス(拡張・3桁連番) 回答あり NoWrap, CSS:code-readonly2
Class153 文字列(拡張分類・3桁連番) 担当者・CC一覧 Hide, 検索窓あり, 複数選択, CSS:field-wide JSON形式Lookup設定あり(①ClassZ経由でClass153同士連携。Class052/077/085/095の絞込元)
ClassY 文字列(分類・1文字) 依頼者・担当者・CC一覧 Hide, 複数選択, CSS:field-wide ユーザー一覧選択(ClassC+Class153の重複除去マージ、10章スクリプト)

4.3 補足HTML(ExtendedHtmlAfterControl)一覧

335410

列名 ラベル HTML内容
ClassA 管理番号 <div class="commentAdd small blue">※自動採番</div>
ClassY マスターシート <div class="commentAdd red nowrap">※マスターシート登録済み物件は、上記より選択し作成ボタンを押すと登録情報が自動反映されます</div>

335411

列名 ラベル HTML内容
Class007 問合せ種別 <div class="commentAdd red nowrap">※最初に問い合わせの種類を選択してください※</div>2026-08-09追加
DescriptionA 問い合わせ経緯 <div class="commentAdd red nowrap">※承認図以降の問い合わせの場合は、「承認図時の内容」であっても必ず経緯を入力してください</div>
Class038 積算Ver. <div class="commentAdd red nowrap">※入力漏れ多いので注意!</div>
Class040 nexthouseシリーズ <div class="commentAdd red nowrap">※nexthouseの場合のみ選択してください※</div> <button type="button" onclick="$p.ex.clearClass040();">選択解除</button>2026-08-09追加。ラジオボタンの選択解除用ボタン
Class045 標準仕様 <div class="commentAdd red nowrap">※標準仕様からの変更差額出しを希望する場合は、こちらに標準仕様を記載してください</div>
Class052 回答希望送付先 <div class="commentAdd blue nowrap">※見積回答を送付して欲しい方を選択してください(選択ない場合は、依頼者と担当営業に回答送信します)</div>
Date064 使用期限 <div class="commentAdd red nowrap">※他物件使用ルールに基づいて使用してください</div>
Date091 使用期限 <div class="commentAdd red nowrap">※他物件使用ルールに基づいて使用してください</div>

5. フィールド単位の権限・ロック制御

5.1 PermissionForCreating / PermissionForUpdating

Site 対象列
335410 Class154(依頼者・担当者・CC一覧) 4
335411 ClassY(依頼者・担当者・CC一覧) 4

いずれも「担当者・CC一覧をマージした複数選択の隠し集約列」に対する作成・更新権限設定。値4のビット意味は環境のロール定義に依存するため断定不可12章参照

5.2 Create/Update/ReadColumnAccessControls部署単位のフィールドアクセス制御

335410:

制御種別 対象列 対象Dept
CreateColumnAccessControls Class199(重複許容) 25(営業積算課)
ReadColumnAccessControls Class199(重複許容) 25(営業積算課)
UpdateColumnAccessControls Class199(重複許容) 25(営業積算課)

→ Class199重複チェック回避弁は営業積算課(Dept25)以外には作成・閲覧・更新いずれも不可。

335411:

CreateColumnAccessControls / UpdateColumnAccessControlsいずれも同一対象列、Dept25・26限定

対象列 ラベル
Description111 引継ぎ備考
Attachments111 NET見積原本
Attachments112 算出根拠資料
Class111 採用書類
Attachments113 その他
Date111 データ出力日

→ Tab-3「積算発注」タブの全項目が対象。ReadColumnAccessControlsの設定はなし閲覧自体はタブ表示制御CSSクラスによるTab-3非表示で担保、5.3節・11章参照。読み取り自体をAPIレベルで禁止してはいない

5.3 StatusControlsStatusに応じた項目ReadOnly化

335410「登録完了後ロック」

  • 対象Status: ["200","900","910","920"](登録完了・依頼終了・保留・取消のいずれか)
  • ReadOnly対象列: Class003, Class004重複チェックキーの姓名カナ、Class022エリア、こちらも重複チェックキー

335411「依頼ロック」

  • 対象Status: ["200","400","450","480","500","510","520","800","810","900","901","910","920"]Status=100「新規」以外のほぼ全状態
  • ReadOnly対象列: ClassE, ClassF, ClassG, ClassH, ClassI, Class007, Class031〜034, Class038〜040, Class045, Class050, Class052, DateA, DescriptionA, Description031, CheckA, Attachments031新規依頼の入力項目一式。Class007は2026-08-09追加。ClassFは既にColumns/EditorColumnHashから削除済みの列だが、ColumnHash上には残存12章参照

→ 新規依頼発行後Status=100を離れた後は依頼内容が改変不可になる設計。「依頼修正」プロセス13章参照でStatusを100へ戻すことで再びロック解除される。

6. 集計設定Aggregations

両サイトとも設定なし(Aggregations: [])。

7. 他サイト連携Linksルックアップ暗黙参照

7.1 SiteSettings.Linksトップレベル

335410:設定なし(Links: [])。

335411

起点列 参照先SiteId 内容
Class003(エリア) 210699 エリアマスタ参照
Class004(ブランド) 109100 契約ブランドマスタ参照
Class086(遅延理由) 335450 遅延理由マスタ。ColumnSorterHash: NumA ascでソート。Lookup: マスタDescriptionA→自サイトDescription081。AutoPostBack: true, ColumnsReturnedWhenAutomaticPostback: Description081遅延理由選択と同時にDescription081へ雛形文を自動転記
ClassZ(テスト・リンク用) 335410 ①物件基本情報。15項目のLookup詳細下表
Class052(回答希望送付先) 0(Usersテーブル直接参照) TableName: Users, ColumnFilterExpressions: {UserId: "=[@Class153]"}Class153担当者・CC一覧に含まれるユーザーのみに絞込選択
Class077(質疑送付先) 0(Usersテーブル直接参照) 同上、Class153絞込
Class085(遅延送付先) 0(Usersテーブル直接参照) 同上、Class153絞込
Class095(回答送付先) 0(Usersテーブル直接参照) 同上、Class153絞込。加えてColumnFilterHash: {Disabled: "false"}(退職者等を除外)

ClassZ→335410 のLookup全15項目

①335410側(From) ②335411側(To) 内容
ClassA ClassA 管理番号→親番
Class032 ClassD 主担当営業
Date001 DateB 承認図予定日
Date002 DateC 着工予定日
Class015 Class001 施主名マージ→施主名
Class021 Class002 建上地
Class022 Class003 エリア
Class023 Class004 契約ブランド→ブランド
Class025 Class005 建物種類
Class024 Class006 防火指定
Class153 Class153 担当者・CC一覧そのまま
Class153 Class052 担当者・CC一覧→回答希望送付先の選択母集団
Class153 Class077 担当者・CC一覧→質疑送付先の選択母集団
Class153 Class085 担当者・CC一覧→遅延送付先の選択母集団
Class153 Class095 担当者・CC一覧→回答送付先の選択母集団

→ ②サイトが新規作成される際①の「新規見積依頼」ボタン経由、この15項目が①から②へ一括コピーされる。ClassZという列名は「テスト・リンク用」というラベルのままだが、実際には①→②の参照キー兼自動転記トリガーとして本番運用されているラベル名と用途の乖離、12章参照

7.2 項目のChoicesTextに埋め込まれたLookup335410

Links配列トップレベルには現れないが、各列のChoicesText内にJSON形式で個別Lookup設定を持つ。

起点列(335410) 参照先SiteId 内容
ClassY(マスターシート) 189112(★マスターシート) SearchFormat: "[ClassA] - [ClassQ] 様 ([ClassF] [ClassG])"ColumnSorterHash: ResultId desc。27項目のLookup下表
Class022(エリア) 210699 Lookup: マスタClassA→自サイトClassMエリア記号OverwriteForm: true
Class023(契約ブランド) 109100 Lookup: マスタClassA→自サイトClassNブランド記号OverwriteForm: true
Class031(契約営業所) 96243 Lookup: マスタClassK→自サイトClass038所属長OverwriteForm: true

ClassY→189112(★マスターシート) のLookup全27項目

マスターシート(From) 335410側(To)
ClassA ClassZ(契約コード)
Class001〜004 Class001〜004(施主1情報)
Class009,010,011,012 Class005,006,007,008(施主2情報)
Class017,018,019,020 Class009,010,011,012(施主3情報)
Class029 Class021(建上地)
ClassW Class022(エリア)
Class033 Class023(契約ブランド)
Class040 Class024(防火指定)
Class043 Class025(建物種類)
ClassF,G,H,I,L,K,J Class031〜037(契約営業所〜担当監督)
Date063 Date001(承認図予定日)
DateH Date002(着工予定日)

いずれもOverwrite: false転記先に既存値があれば上書きしない。実際の転記処理はサーバー側AutoPostBackではなく、10章記載のクライアントスクリプト($p.apiGetによるその場取得)で行われる。

7.3 ServerScriptがSiteIdを直接ハードコード参照している暗黙的テーブル参照

正式なLinks設定には現れない、items.Get(SiteId)による直接参照。

サイト ServerScriptファイル 参照先SiteId 目的
335410 serverscripts/1_マスターデータ取得.js 210699 エリアマスタ全件取得→hidden格納クライアント側Class022変更時の逆引き転記用
335410 serverscripts/1_マスターデータ取得.js 109100 契約ブランドマスタ全件取得→hidden格納Class023変更時の逆引き転記用
335410 serverscripts/1_マスターデータ取得.js 96243 契約営業所マスタ全件取得→hidden格納Class031変更時の逆引き転記用
335411 serverscripts/1_祝日リスト取得スクリプト.js 192267(祝日テーブル) 本日30日〜+60日の祝日一覧取得→hidden格納DateA/DateJ/Date073等の営業日計算用

7.4 サイトツリー上の位置関係

74663親サイト、詳細不明・本仕様書の対象外
├─ 335410 ①物件基本情報 Ver0.3
└─ 335411 ②問い合わせ内容管理 Ver0.3

335410↔335411は上記7.1のClassZ Linksで対等連携。加えて両サイトとも下記の外部マスタサイトを参照

189112 ★マスターシート ← 335410(ClassY)
210699 エリアマスタ     ← 335410(Class022) / 335411(Class003, Links)
109100 契約ブランドマスタ ← 335410(Class023) / 335411(Class004, Links)
96243  契約営業所マスタ  ← 335410(Class031)
192267 祝日テーブル      ← 335411(ServerScript直接参照)
335450 遅延理由マスタ    ← 335411(Class086, Links)

8. プロセス設定Processes・ワークフロー・通知設定

定義ファイル:335410/processes.json 335411/processes.json

Status選択肢ChoicesText由来、コード,正式名称,一覧表示名,色クラスの順):

335410 Status: 100,新規登録,新規,new-status-white / 200,登録完了,登録,new-status-pink / 900,依頼終了,依頼,new-status-blue / 910,保留,保留,new-status-grey / 920,取消,取消,new-status-grey

335411 StatusClass062と共通: 100,新規,新規,white / 200,新規依頼,新規依頼,light-orange / 400,受付済,受付済,orange / 450,依頼者質疑中,質疑中,yellow / 480,依頼者回答有,回答有,white / 500,積算対応中,積算対応中,green / 510,外部見積待ち,外部見積待ち,green / 520,社内確認中,社内確認中,green / 800,一部回答,一部回答,pink / 810,回答完了,回答完了,light-blue / 900,完了,完了,blue / 901,問合せ不要,問合せ不要,blue / 910,保留,保留,grey / 920,取消,取消,grey

8.1 335410 プロセス一覧全5件

Id Name CurrentStatus→ChangedStatus 実行者条件 入力チェック 確認ダイアログ 通知
1 登録完了 100→200 ClassB(登録者)="Own"(本人限定) Class001〜004,021〜023,031,032必須 あり あり(1件)
2 依頼受付終了 200→900 Dept25 なし あり あり(1件)
3 物件保留 -1(任意)→910 Dept25 なし あり あり(1件)
4 物件取消 -1(任意)→920 Dept25 なし あり あり(1件)
5 登録完了へ戻す -1(Status=910/920から, ColumnFilterHash指定)→200 Dept25 なし あり あり(1件)

通知詳細335410

  • 登録完了:宛先e-sekisan@next-hd.co.jp固定。件名【営業積算】[ClassA] [Class001]様 : 物件登録完了。本文「物件情報の登録が完了しました。内容をご確認ください。{Url} ---------- 管理番号:[ClassA] 施主名:[Class001] [Class002]様 ----------」
  • 依頼受付終了:宛先[ClassB](登録者本人)、CCe-sekisan@next-hd.co.jp。件名…依頼受付終了。本文「営業積算より本件について依頼受付を終了しましたのでご連絡します。」送信専用文言URL管理番号/施主名
  • 物件保留:宛先[ClassB]、CCe-sekisan@next-hd.co.jp。件名…物件保留。本文「本件を保留といたしました」
  • 物件取消:宛先[ClassB]、CCe-sekisan@next-hd.co.jp。件名…物件取消。本文「本件を取消といたしました」
  • 登録完了へ戻す:宛先[ClassB]、CCe-sekisan@next-hd.co.jp。件名…登録完了へ再設定。本文「保留・取消を解除し、登録完了へ戻しました」

全通知共通:Type:1(メール)、MediaType: application/jsonBeforeCondition/AfterCondition/Expression: 0

335410 ワークフロー図Mermaid

flowchart LR
  100((新規登録<br/>100))
  200((登録完了<br/>200))
  900((依頼終了<br/>900))
  910((保留<br/>910))
  920((取消<br/>920))
  any((任意))

  100 -->|"📧登録完了"| 200
  200 -->|"📧依頼受付終了 (Dept25)"| 900
  any -->|"📧物件保留 (Dept25)"| 910
  any -->|"📧物件取消 (Dept25)"| 920
  910 -->|"📧登録完了へ戻す (Dept25)"| 200
  920 -->|"📧登録完了へ戻す (Dept25)"| 200

8.2 335411 プロセス一覧全19件

Id Name Current→Changed 実行者条件 入力チェック 通知
1 積算へ依頼2026-08-09改称、旧名称「新規依頼」 100→200 ClassC="Own" なし旧ValidateInputs 12項目は2026-08-09全削除。問合せ種別(Class007)に応じた必須チェックはスクリプト側checkInquiryTypeRequiredFields()へ移行、10章参照 あり
2 受付 200→400 Dept25 なし なし受付日Date081・受付者Class081は2026-08-09よりプロセス側「データの変更」ではなくクリック時にスクリプト側setTodayAndUser()で設定、10章参照
3 質疑送信 -1(Status:100,400,450,480,500,510,520,800)→450 Dept25 なし(スクリプト側でDescription075必須チェック) あり
4 回答送信 450→480 ClassY="Own"(依頼者・担当者・CC一覧に本人含む) なし(スクリプト側でDescription076必須チェック、かつuserId=ClassC二重判定) あり
5 質疑回答削除 450→450 Dept25 なし(スクリプト側でチェック選択必須) あり
6 積算対応中へ -1(Status:400,450,480,500,510,520)→500 Dept25 なし なし
7 外部見積待ちへ -1(同上範囲)→510 Dept25 なし なし見積依頼日Date082・見積依頼者Class082は2026-08-09よりスクリプト側setTodayAndUser()で設定)
8 社内確認中へ -1(同上範囲)→520 Dept25 なし なし
9 一部回答 -1(Status:400,450,480,500,510,520,800)→800 Dept25 なし あり返信日Date103・返信者Class103は2026-08-09よりスクリプト側setTodayAndUser()で設定)
10 見積回答 -1(Status:400,450,480,500,510,520,800,810)→810 Dept25 なし あり返信日Date103・返信者Class103は2026-08-09よりスクリプト側setTodayAndUser()で設定。以前あったクライアント側activate/disable制御(estimateReplyEnabledStatuses)は2026-08-09撤去、実行可否は本Process View条件のみに一本化
11 完了 -1(Status:800,810)→900 Dept25 なし なし
12 問い合わせ不要 -1(Status:200,400,450,480,500,510,520)→901 Dept25 なし あり
13 依頼修正 200→100 ClassC="Own" なし なし
14 差戻し -1(Status:400,450,480,500,510,520)→100 Dept25 なし あり
15 依頼取消 -1(Status:400,450,480,500,510,520,910)→920 Dept25 なし あり
16 保留 -1(Status:400,450,480,500,510,520)→910 Dept25 なし なし
17 保留解除 910→500 Dept25 なし なし
18 遅延連絡 -1(Status:400,450,480,500,510,520,800)→-1(状態変化なし) Dept25 なし(スクリプト側でDescription081必須チェック) あり遅延連絡日Date084・遅延連絡者Class087は2026-08-09よりスクリプト側setTodayAndUser()で設定)
19 ①物件基本情報へ戻る -1→-1(状態変化なし、ActionType:90=画面遷移のみ) 制限なし なし なし

通知詳細335411、あり=Type:1メール、MediaType application/json共通

  • 積算へ依頼(旧名称:新規依頼):宛先e-sekisan@next-hd.co.jp、CC[ClassC](依頼者)。件名【営業積算】[ClassA]-[ClassB] [Class001]様 : 新規依頼通知の件名・本文はDisplayName改称の影響を受けず現状維持。本文「依頼者より新規依頼が届きました。内容を確認し、受付処理をお願いします。」URL親番/子番/施主名
  • 質疑送信:宛先[ClassC]、CC[Class077](質疑送付先)。件名…質疑送信。本文「営業積算より依頼内容についての質疑が届きました。」+質疑回答期日[Date073]+質疑内容[Description075]
  • 回答送信:宛先e-sekisan@next-hd.co.jp、CC[ClassC], [Class077]。件名…回答送信。本文「依頼者より質疑回答が届きました。」+回答内容[Description076]
  • 質疑回答削除:宛先[ClassC]、CC[Class077]。件名…質疑回答削除。本文「質疑回答の内容が削除されました。再度ご回答をお願いする場合がございます。」
  • 一部回答:宛先[ClassC]、CC[Class095](回答送付先)。件名…一部回答。本文「一部見積回答が届きました。([Num101]回目の回答)」+見積回答[Description091]+別途費用[Description092]
  • 見積回答:宛先[ClassC]、CC[Class095]。件名…見積回答。本文構成は一部回答と同一(「見積回答が届きました」)
  • 問い合わせ不要:宛先[ClassC]、CC[Class052](回答希望送付先)。件名…問い合わせ不要。本文「本件について問い合わせ不要と判断いたしました」
  • 差戻し:宛先[ClassC]のみ(CCなし)。件名…差戻し。本文「依頼内容の見直しをお願いしたく、差し戻しいたします。」
  • 依頼取消:宛先[ClassC]、CC[Class052]。件名…依頼取消。本文「依頼者からの申し出により依頼取消となりました」
  • 遅延連絡:宛先[ClassC]、CC[Class085](遅延送付先)。件名…遅延連絡。本文「見積回答遅延のご連絡です。」+遅延理由[Class086]+返信予定日[Date085]+遅延連絡内容[Description081]

いずれも「※このメールは送信専用です。本メールに返信いただいても、内容は確認できませんのでご了承ください。お問い合わせは、営業積算窓口までご連絡ください。」の定型文を含む(新規依頼・質疑回答削除・問い合わせ不要・依頼取消は定型文なし版)。

335411 ワークフロー図Mermaid

flowchart LR
  100((新規<br/>100))
  200((新規依頼<br/>200))
  400((受付済<br/>400))
  450((依頼者質疑中<br/>450))
  480((依頼者回答有<br/>480))
  500((積算対応中<br/>500))
  510((外部見積待ち<br/>510))
  520((社内確認中<br/>520))
  800((一部回答<br/>800))
  810((回答完了<br/>810))
  900((完了<br/>900))
  901((問合せ不要<br/>901))
  920((取消<br/>920))
  910((保留<br/>910))
  any((任意))

  100 -->|"📧積算へ依頼"| 200
  200 -->|"受付 (Dept25)"| 400
  100 -->|"📧質疑送信 (Dept25)"| 450
  400 -->|"📧質疑送信 (Dept25)"| 450
  450 -->|"📧質疑送信 (Dept25)"| 450
  480 -->|"📧質疑送信 (Dept25)"| 450
  500 -->|"📧質疑送信 (Dept25)"| 450
  510 -->|"📧質疑送信 (Dept25)"| 450
  520 -->|"📧質疑送信 (Dept25)"| 450
  800 -->|"📧質疑送信 (Dept25)"| 450
  450 -->|"📧回答送信"| 480
  450 -->|"📧質疑回答削除 (Dept25)"| 450
  400 -->|"積算対応中へ (Dept25)"| 500
  450 -->|"積算対応中へ (Dept25)"| 500
  480 -->|"積算対応中へ (Dept25)"| 500
  500 -->|"積算対応中へ (Dept25)"| 500
  510 -->|"積算対応中へ (Dept25)"| 500
  520 -->|"積算対応中へ (Dept25)"| 500
  400 -->|"外部見積待ちへ (Dept25)"| 510
  450 -->|"外部見積待ちへ (Dept25)"| 510
  480 -->|"外部見積待ちへ (Dept25)"| 510
  500 -->|"外部見積待ちへ (Dept25)"| 510
  510 -->|"外部見積待ちへ (Dept25)"| 510
  520 -->|"外部見積待ちへ (Dept25)"| 510
  400 -->|"社内確認中へ (Dept25)"| 520
  450 -->|"社内確認中へ (Dept25)"| 520
  480 -->|"社内確認中へ (Dept25)"| 520
  500 -->|"社内確認中へ (Dept25)"| 520
  510 -->|"社内確認中へ (Dept25)"| 520
  520 -->|"社内確認中へ (Dept25)"| 520
  400 -->|"📧一部回答 (Dept25)"| 800
  450 -->|"📧一部回答 (Dept25)"| 800
  480 -->|"📧一部回答 (Dept25)"| 800
  500 -->|"📧一部回答 (Dept25)"| 800
  510 -->|"📧一部回答 (Dept25)"| 800
  520 -->|"📧一部回答 (Dept25)"| 800
  800 -->|"📧一部回答 (Dept25)"| 800
  400 -->|"📧見積回答 (Dept25)"| 810
  450 -->|"📧見積回答 (Dept25)"| 810
  480 -->|"📧見積回答 (Dept25)"| 810
  500 -->|"📧見積回答 (Dept25)"| 810
  510 -->|"📧見積回答 (Dept25)"| 810
  520 -->|"📧見積回答 (Dept25)"| 810
  800 -->|"📧見積回答 (Dept25)"| 810
  810 -->|"📧見積回答 (Dept25)"| 810
  800 -->|"完了 (Dept25)"| 900
  810 -->|"完了 (Dept25)"| 900
  200 -->|"📧問い合わせ不要 (Dept25)"| 901
  400 -->|"📧問い合わせ不要 (Dept25)"| 901
  450 -->|"📧問い合わせ不要 (Dept25)"| 901
  480 -->|"📧問い合わせ不要 (Dept25)"| 901
  500 -->|"📧問い合わせ不要 (Dept25)"| 901
  510 -->|"📧問い合わせ不要 (Dept25)"| 901
  520 -->|"📧問い合わせ不要 (Dept25)"| 901
  200 -->|"依頼修正"| 100
  400 -->|"📧差戻し (Dept25)"| 100
  450 -->|"📧差戻し (Dept25)"| 100
  480 -->|"📧差戻し (Dept25)"| 100
  500 -->|"📧差戻し (Dept25)"| 100
  510 -->|"📧差戻し (Dept25)"| 100
  520 -->|"📧差戻し (Dept25)"| 100
  400 -->|"📧依頼取消 (Dept25)"| 920
  450 -->|"📧依頼取消 (Dept25)"| 920
  480 -->|"📧依頼取消 (Dept25)"| 920
  500 -->|"📧依頼取消 (Dept25)"| 920
  510 -->|"📧依頼取消 (Dept25)"| 920
  520 -->|"📧依頼取消 (Dept25)"| 920
  910 -->|"📧依頼取消 (Dept25)"| 920
  400 -->|"保留 (Dept25)"| 910
  450 -->|"保留 (Dept25)"| 910
  480 -->|"保留 (Dept25)"| 910
  500 -->|"保留 (Dept25)"| 910
  510 -->|"保留 (Dept25)"| 910
  520 -->|"保留 (Dept25)"| 910
  910 -->|"保留解除 (Dept25)"| 500
  400 -->|"📧遅延連絡 (Dept25)"| 400
  450 -->|"📧遅延連絡 (Dept25)"| 450
  480 -->|"📧遅延連絡 (Dept25)"| 480
  500 -->|"📧遅延連絡 (Dept25)"| 500
  510 -->|"📧遅延連絡 (Dept25)"| 510
  520 -->|"📧遅延連絡 (Dept25)"| 520
  800 -->|"📧遅延連絡 (Dept25)"| 800
  any -->|"①物件基本情報へ戻る"| any

8.3 プロセスボタンのUI上の扱い335411固有

335411は19プロセスのうち12件受付/Id2、質疑送信/Id3、回答送信/Id4、質疑回答削除/Id5、積算対応中へ/Id6、外部見積待ちへ/Id7、社内確認中へ/Id8、一部回答/Id9、見積回答/Id10、問い合わせ不要/Id12、差戻し/Id14、遅延連絡/Id18について、ServerScript1_祝日リスト取得スクリプト.jselements.DisplayType('Process_N', 3)でPleasanter標準のプロセスボタン自体を非表示化2026-08-09、従来6件から12件に拡大し、代わりに3_質疑応答スクリプト.jsが対応する独自ボタンを動的生成・各項目欄の右横(または直下)へ配置している。独自ボタンのクリックは$p.ex.clickProcessByName(name)でラベル名一致の隠れた標準ボタンを探して.click()する方式Processes側のId構成変更に影響されない設計

独自ボタンの生成条件2026-08-09変更:以前は一部のボタン(営業積算確認・遅延連絡・見積回答・質疑回答削除)が$p.deptId()=='25'のハードコード判定(eSekisanFlag)で表示可否を決めていたが、対応する標準プロセスボタンがelements.DisplayType(id,3)「invisible」DOM上には残るが見た目のみ非表示であることを踏まえ、DOM実在判定$p.ex.processButtonExists('プロセス名')Depts・View条件にそのまま追従、部署IDのハードコードなしに全面統一した。これにより、Process設定側のDepts変更が独自ボタンの表示可否へ自動反映される。

独自ボタンの配置一覧2026-08-09時点

ボタン ラベル 配置先 生成関数
質疑送信 質疑送信 質疑ファイル(Attachments071)欄の下 messageSaveButton()
回答送信 回答送信 回答ファイル(Attachments072)欄の下 messageSaveButton()
積算対応中へ 営業積算確認(ボタン表示名のみ独自) 回答ファイル(Attachments072)欄の下 messageSaveButton()
質疑回答削除 質疑回答削除 質疑内容(Description075)欄の上 messageHistory()
受付 受付 受付者(Class081)欄の右横 receiptButton()2026-08-09新設
外部見積待ちへ 外部見積待ちへ 見積依頼者(Class082)欄の右横 externalEstimateWaitButton()2026-08-09新設
社内確認中へ・問い合わせ不要・差戻し 社内確認中へ/問い合わせ不要/差戻し 状況(Status)欄の右横に一列 statusRowButtons()2026-08-09新設
一部回答 一部回答 返信者(Class103)欄の右横(上段) partialAnswerButton()2026-08-09新設
見積回答 見積回答 返信者(Class103)欄の右横(一部回答の直下) estimateReplyButton()2026-08-09、配置先をDescription092欄の下から移動
遅延連絡 遅延連絡 遅延連絡内容(Description081)欄の下 delayNoticeButton()(配置先は変更なし)

一部回答・見積回答はいずれも実行可否をProcess側View条件(Status)のみに委ね、クライアント側でのactivate/disable制御は行わない2026-08-09、見積回答のestimateReplyEnabledStatusesによる独自disable判定を撤去

9. スタイルStyles全文

9.1 3354102ファイル

Id Title 適用範囲 ファイル
1 01_読み取り専用スタイル New/Edit styles/1_01.読み取り専用.css
2 02_共通スタイル New/Edit styles/2_02.共通.css

1_01.読み取り専用.css

/*
@config : next999.json
@filename : 01_読み取り専用スタイル.css
@title : 01_読み取り専用スタイル
@name :
@siteIds : 335101
@siteTitles : ①物件基本情報
@disabled: false
*/
label.error {
    pointer-events: none;
    z-index: 100;
    border-top: none !important;
}

.code-readonly {
    pointer-events: none;
    background-color: lightgrey;
    font-weight: bold;
}

.code-readonly2,
.code-readonly2+button,
.code-readonly2+div.ui-icon,
.code-readonly3,
.code-readonly3+button,
.code-readonly3+div.ui-icon {
    font-weight: bold;

    &:active {
        pointer-events: none;
    }

    a:not(.ui-spinner-button) {
        pointer-events: auto;
    }

}

.code-readonly2 select,
.code-readonly2 input,
.code-readonly2 button.ui-multiselect,
.code-readonly2 .control-markup,
.code-readonly2 .ui-spinner,
span[data-readonly="1"] {
    pointer-events: none;

    border: dashed 1px black;
    background-color: lightgray !important;
    color: black !important;

    .ui-spinner-input {
        border: none;
    }
}

.code-readonly3 select,
.code-readonly3 input,
.code-readonly3 button.ui-multiselect,
.code-readonly3 .control-markup,
.code-readonly3 .ui-spinner {
    pointer-events: none;

    border: dashed 1px black;

    .ui-spinner-input {
        border: none;
    }
}

.code-readonly2,
.code-readonly3,
.field-normal:has(span[data-readonly="1"]),
.field-wide:has(span[data-readonly="1"]) {
    position: relative;

    &:hover::after {
        content: "読取り専用です";
        position: absolute;
        left: 120px;
        top: calc(100% - 9px);
        margin-top: 4px;
        background: #00000080;
        color: #fff;
        padding: 4px 10px;
        border-radius: 4px;
        font-size: 12px;
        font-weight: normal !important;
        pointer-events: none;
        z-index: 9999;
        white-space: nowrap;
    }
}

.code-required,
select[data-validate-required="1"],
input[data-validate-required="1"] {
    border: double 3px red !important;

    &+button.ui-multiselect {
        border: double 3px red !important;
    }

}

.field-normal:has(.code-required),
.field-wide:has(.code-required),
.field-normal:has(select[data-validate-required="1"]),
.field-wide:has(select[data-validate-required="1"]),
.field-normal:has(input[data-validate-required="1"]),
.field-wide:has(input[data-validate-required="1"]) {
    position: relative;

    &:hover::after {
        content: "入力必須です";
        position: absolute;
        left: 120px;
        top: calc(100% - 9px);
        margin-top: 4px;
        background: #ff000080;
        color: #fff;
        padding: 4px 10px;
        border-radius: 4px;
        font-size: 12px;
        font-weight: normal !important;
        pointer-events: none;
        z-index: 9999;
        white-space: nowrap;
    }
}

.error {
    border: double 3px red !important;
}

2_02.共通.css

/*
@config : next999.json
@filename : 02_共通スタイル.css
@title : 02_共通スタイル
@name :
@siteIds : 335101
@siteTitles : ①物件基本情報
@disabled: false
*/
/*見出しサイズ*/
.field-section {
    font-size: 1.5em !important;
    font-weight: bold !important;
    height: auto;
}

/*項目の後ろの行間を少し狭める*/
.field-normal {
    height: 35px;
}

.field-wide {
    min-height: 35px;
    padding: 0 10px 5px 0;
}

/*コメントのある項目の高さだけ少し広げる*/
div:has(.commentAdd).field-normal {
    height: 50px;
}

9.2 3354113ファイル

Id Title 適用範囲 ファイル
1 00_読み取り専用スタイル New/Edit styles/1_00_読み取り専用スタイル.css.css
2 01_共通スタイル New/Edit styles/2_01_共通スタイル.css.css
3 04_質疑回答テーブル New/Edit styles/3_3_04_質疑回答テーブル.css.css

1_00_読み取り専用スタイル.css.css

335410の1_01.読み取り専用.css内容は完全に同一コメントヘッダのfilename記述のみ異なる00_読み取り専用スタイル.css。重複掲載は省略、9.1節参照。

2_01_共通スタイル.css.css

/*
@config :
@filename : ${psFileBasename}
@title : 01_共通スタイル
@name
@siteIds :
@siteTitles : 物件基本情報
@disabled: false
*/

/*見出しサイズ*/
.field-section {
    font-size: 1.5em !important;
    font-weight: bold !important;
    height: auto;
}

/*項目の後ろの行間を少し狭める*/
.field-normal {
    height: 35px;
}

.field-wide {
    min-height: 35px;
    padding: 0 10px 5px 0;
}

/*コメントのある項目の高さだけ少し広げる*/
div:has(.commentAdd).field-normal {
    height: 50px;
}

.dateA_alert {
    background-color: red !important;
    color: white !important;
}

/*「営業積算」タブ_Tab-2/FieldSetTab2既定非表示。営業積算課(Dept25)のみ01_基本スクリプト.jsが
  bodyへshow-sekisan-tabクラスを付与し表示化する01_基本スクリプト.js側の判定と対*/
#EditorTabs li[aria-controls="FieldSetTab2"] {
    display: none !important;
}

#FieldSetTab2 {
    display: none !important;
}

body.show-sekisan-tab #EditorTabs li[aria-controls="FieldSetTab2"] {
    display: list-item !important;
}

body.show-sekisan-tab #FieldSetTab2 {
    display: block !important;
}

/*「積算発注」タブ_Tab-3/FieldSetTab3既定非表示。dept25/26/28/66/36所属者またはuserId 913/987/1153のみ
  bodyへshow-sekisan-hachu-tabクラスを付与し表示化する01_基本スクリプト.js側の判定と対*/
#EditorTabs li[aria-controls="FieldSetTab3"] {
    display: none !important;
}

#FieldSetTab3 {
    display: none !important;
}

body.show-sekisan-hachu-tab #EditorTabs li[aria-controls="FieldSetTab3"] {
    display: list-item !important;
}

body.show-sekisan-hachu-tab #FieldSetTab3 {
    display: block !important;
}

3_3_04_質疑回答テーブル.css.css

/*
@config :
@filename : 04_質疑回答テーブル.css
@title : 04_質疑回答テーブル
@name :
@siteIds :
@siteTitles : 問合せ内容管理
@disabled: false
*/

#messageTable {
    width: 98%;
    margin: 8px;
    border-collapse: separate !important;
}

.radius-table {
    border-radius: 10px !important;
    border-spacing: 0;
    border: none;
    border-left: 1px solid #999;
    border-top: 1px solid #999;
}

.radius-table tr>* {
    padding: 5px 10px;
    border: none;
    border-right: 1px solid #999;
    border-bottom: 1px solid #999;
}

.radius-table tr:first-child>*:first-child {
    border-radius: 10px 0 0 0;
}

.radius-table tr:first-child>*:last-child {
    border-radius: 0 10px 0 0;
}

.radius-table tr:last-child>*:first-child {
    border-radius: 0 0 0 10px;
}

.radius-table tr:last-child>*:last-child {
    border-radius: 0 0 10px 0;
}

.Question>td {
    background-color: coral;
    vertical-align: middle;
}

.Question\.Disabled>td,
.Answer\.Disabled>td {
    background-color: grey !important;
}

.Answer>td {
    background-color: yellow;
    vertical-align: middle;

}

.shitsugi_type {
    font-weight: bold;
    font-size: 1.5em;
}

.shitsugi_userName {
    text-align: center;
    width: 150px;
    font-size: 1.2em;
}

.shitsugi_messageTime {
    text-align: right;
    font-size: 1.2em;
}

.shitsugi_messageBody {
    background-color: white !important;
}

.shitsugi_deleteCheck {
    background-color: white !important;
    text-align: center;
    width: 80px;
}

.shitsugi_preWrap {
    white-space: pre-wrap;
    text-overflow: ellipsis;
    break-inside: avoid;
}

10. スクリプトScriptsサーバースクリプトServerScripts全文

10.1 335410 Scripts2ファイル

Id Title 適用範囲 概要
1 01_基本スクリプト New/Edit 施主名・重複チェックキー管理、マスターシート/エリア/契約ブランド/契約営業所の自動転記、案件状況(ClassS)自動判定、担当者一覧マージ、readonly項目のTab移動除外、ボタン名称変更・表示制御
2 02_プロセス処理スクリプト New/Edit プロセスボタン共通ディスパッチャ(processScript)、確認ダイアログ制御、必須エラー項目の集約表示

scripts/1_01_基本スクリプト.js.js

/*
@config :
@filename : ${psFileBasename}
@title : 01_基本スクリプト
@name :
@siteIds :
@siteTitles : 物件基本情報
@disabled: false
*/

let pageLoading = 0;

//----------------------------------------------------------------------
//初期処理
window.addEventListener('load', function () {
    if (pageLoading == 0) {
        console.log('Window load Event');
        $p.ex.onLoad();
    }
});

$p.events.on_editor_load = function () {
    if (pageLoading == 0) {
        console.log('on Editor Load');
        $p.ex.onLoad();
    }
}

//----------------------------------------------------------------------
$p.ex.onLoad = function () {
    //ロード開始
    pageLoading = 1;

    //初期処理
    $p.ex.init();

}

//初期処理
$p.ex.init = function () {
    console.log('initialize...');

    //ロード時に施主名を予め控えておく
    $p.loadCustomerName();

    //依頼発行ボタン名称変更
    $p.changeButtonName();

    //登録状況Statusに応じて依頼発行ボタンの表示・非表示を切り替え
    $p.toggleNewRequestButton();

    //ServerScriptが事前取得したエリア・契約ブランド・契約営業所マスタをhiddenから読み込む
    $p.loadStaticMasters();

    //マスターシート・エリア・契約ブランド・契約営業所の現在値を「変更前の値」として記憶
    //(未選択→選択のときは既存値を保持、選択済み→別選択のときは上書き、を判定するため)
    $p.rememberLookupBaseValues();

    //登録完了(Status=200)以降、マスターシートが選択済みの場合のみ選択変更を不可にする
    //(未選択のままなら登録完了後も選択可能にする)
    $p.toggleClassYLock();

    //code-readonly2読取り専用が設定されている全フィールドをTab移動対象から除外する
    //ClassYの動的ロックも含め、上記の処理が確定した後に実行する
    $p.applyReadonlyTabindex();
}

//入力チェック後処理
$p.events.after_validate = function (args) {
    //担当者一覧のマージ
    $p.mergeUserList();
}

//画面更新後
$p.events.after_set_Update = function (args) {
    //初期処理
    $p.ex.init();
}

//----------------------------------------------------------------------
//施主名を控える
let customerName = {
    'firstName': '',
    'lastName': '',
    'firstNameKana': '',
    'lastNameKana': ''
}

//ロード時に施主名を予め控えておく
$p.loadCustomerName = function () {
    customerName.firstName = $('#Results_Class002').val();   //施主1・名
    customerName.lastName = $('#Results_Class001').val();   //施主1・姓
    customerName.firstNameKana = $('#Results_Class004').val();   //施主1・名カナ
    customerName.lastNameKana = $('#Results_Class003').val();   //施主1・姓カナ
}

//----------------------------------------------------------------------
//入力チェック前処理
//更新ボタンを押したあと、入力必須項目などのチェックの前に処理される関数
$p.events.before_validate = function (args) {
    //新規作成時のみ、重複チェックキー(施主氏名カナ・エリア)の最終確認を促す
    //(登録完了処理後、この項目は編集不可になるため、新規作成の保存前に一度だけ確認する)
    if ($p.action() === 'new') {
        if (!confirm($p.buildNewRecordKeyConfirmMessage())) {
            return false; //保存中断
        }
    }

    //施主名がどれか1つでも変更されていたら処理する
    if (customerName.firstName != $('#Results_Class002').val() || customerName.lastName != $('#Results_Class001').val() || customerName.firstNameKana != $('#Results_Class004').val() || customerName.lastNameKana != $('#Results_Class003').val()) {
        $p.mergeCustomerName();
    }

    //担当者一覧のマージ
    $p.mergeUserList();

    //重複チェック用の連結値をClass200へ保存Class200はNoDuplication設定済みのため、
    //保存時にPleasanter標準機能が重複判定・アラート表示・保存ブロックを行う
    $p.mergeClass200();
}

//----------------------------------------------------------------------
//重複チェック用に施主1・姓カナ施主1・名カナエリア重複許容をClass200へ連結する
//主担当営業・契約営業所は人事異動等で変わりうるためキーに含めない(同一物件でも担当者交代で
//別レコード扱いになってしまうのを防ぐ)。エリアは物件の位置そのもので不変のためキーに採用。
//エリアが異なれば同姓同名でも別物件として許容する。
//Class199重複許容、営業積算専用・通常空欄は、同一エリアに同姓同名の別物件が実在する
//稀なケースの回避弁。営業積算が「重複1」「重複10」のいずれかを設定すると、その値も
//キーに含まれるため重複チェックをすり抜けられる同一エリア同姓同名は最大10件まで想定$p.mergeClass200 = function () {
    let str1 = $p.normalizeForDuplicationKey($('#Results_Class003').val());   //施主1・姓カナ
    let str2 = $p.normalizeForDuplicationKey($('#Results_Class004').val());   //施主1・名カナ
    let str3 = $p.normalizeForDuplicationKey($('#Results_Class022').val());   //エリア
    let str4 = $('#Results_Class199').val() || '';   //重複許容(営業積算専用)
    $p.set($('#Results_Class200'), str1 + str2 + str3 + str4);   //重複チェック
}

//----------------------------------------------------------------------
//重複チェックキー用の文字列正規化
//カナ項目はClientRegexValidation^[ァ-ヶー0-0-9  ]+$)で全角カタカナ・長音・数字・スペース以外
//入力できない設計だが、半角カナはこの正規表現をすり抜けるため変換する。ひらがな・全角英数字の変換は
//保険通常入力では発生しないがAPI経由投入等に備える$p.normalizeForDuplicationKey = function (str) {
    if (!str) return '';
    let s = String(str);

    //半角カナ→全角カナ変換(濁点・半濁点合成)
    s = $p.halfKanaToFullKana(s);

    //ひらがな→全角カタカナ変換
    s = s.replace(/[ぁ-ゖ]/g, function (match) {
        return String.fromCharCode(match.charCodeAt(0) + 0x60);
    });

    //全角英数字→半角変換
    s = s.replace(/[---]/g, function (match) {
        return String.fromCharCode(match.charCodeAt(0) - 0xFEE0);
    });

    //半角スペース→全角スペースに統一
    s = s.replace(/ /g, ' ');

    //記号除去(氏名区切りとして使われる中黒「・」と長音「ー」は残す)
    s = s.replace(/[!-\/:-@\[-`{-~----]/g, function (match) {
        return (match === '・') ? match : '';
    });

    return s;
}

//半角カナ文字列を全角カナへ変換する(濁点・半濁点はゼロ幅文字゙゚として後続するため合成する)
$p.halfKanaToFullKana = function (str) {
    let kanaMap = {
        'ア': 'ア', 'イ': 'イ', 'ウ': 'ウ', 'エ': 'エ', 'オ': 'オ',
        'カ': 'カ', 'キ': 'キ', 'ク': 'ク', 'ケ': 'ケ', 'コ': 'コ',
        'サ': 'サ', 'シ': 'シ', 'ス': 'ス', 'セ': 'セ', 'ソ': 'ソ',
        'タ': 'タ', 'チ': 'チ', 'ツ': 'ツ', 'テ': 'テ', 'ト': 'ト',
        'ナ': 'ナ', 'ニ': 'ニ', 'ヌ': 'ヌ', 'ネ': 'ネ', 'ノ': '',
        'ハ': 'ハ', 'ヒ': 'ヒ', 'フ': 'フ', 'ヘ': 'ヘ', 'ホ': 'ホ',
        'マ': 'マ', 'ミ': 'ミ', 'ム': 'ム', 'メ': 'メ', 'モ': 'モ',
        'ヤ': 'ヤ', 'ユ': 'ユ', 'ヨ': 'ヨ',
        'ラ': 'ラ', 'リ': 'リ', 'ル': 'ル', 'レ': 'レ', 'ロ': 'ロ',
        'ワ': 'ワ', 'ヲ': 'ヲ', 'ン': 'ン',
        'ァ': 'ァ', 'ィ': 'ィ', 'ゥ': 'ゥ', 'ェ': 'ェ', 'ォ': 'ォ',
        'ッ': 'ッ', 'ャ': 'ャ', 'ュ': 'ュ', 'ョ': 'ョ', 'ー': 'ー'
    };
    let dakutenMap = { 'カ': 'ガ', 'キ': 'ギ', 'ク': 'グ', 'ケ': 'ゲ', 'コ': 'ゴ', 'サ': 'ザ', 'シ': 'ジ', 'ス': 'ズ', 'セ': 'ゼ', 'ソ': 'ゾ', 'タ': 'ダ', 'チ': 'ヂ', 'ツ': 'ヅ', 'テ': 'デ', 'ト': 'ド', 'ハ': 'バ', 'ヒ': 'ビ', 'フ': 'ブ', 'ヘ': 'ベ', 'ホ': 'ボ' };
    let handakutenMap = { 'ハ': 'パ', 'ヒ': 'ピ', 'フ': 'プ', 'ヘ': 'ペ', 'ホ': 'ポ' };

    let result = '';
    for (let i = 0; i < str.length; i++) {
        let ch = str[i];
        let next = str[i + 1];
        let full = kanaMap[ch];
        if (full === undefined) {
            result += ch;
            continue;
        }
        if (next === '゙' && dakutenMap[full]) {
            result += dakutenMap[full];
            i++;
        } else if (next === '゚' && handakutenMap[full]) {
            result += handakutenMap[full];
            i++;
        } else {
            result += full;
        }
    }
    return result;
}

//----------------------------------------------------------------------
//新規作成時の最終確認メッセージ(重複チェックキーは登録完了後に編集不可になるための警告)
$p.buildNewRecordKeyConfirmMessage = function () {
    let seiKana = $('#Results_Class003').val() || '(未入力)';
    let meiKana = $('#Results_Class004').val() || '(未入力)';
    let areaText = $('#Results_Class022 option:selected').text().trim() || '(未選択)';
    return '新規作成します。\n\n以下の内容は、登録完了処理後は変更できません。内容に間違いがないか最終確認をお願いします。\n\n施主氏名カナ' + seiKana + ' ' + meiKana + '\nエリア' + areaText + '\n\nよろしいですか';
}

//----------------------------------------------------------------------
//施主名をマージする
$p.mergeCustomerName = function () {
    //4つの項目を1つにマージする
    let str1 = $('#Results_Class001').val();   //施主1・姓
    let str2 = $('#Results_Class002').val();   //施主1・名
    let str3 = $('#Results_Class003').val();   //施主1・姓カナ
    let str4 = $('#Results_Class004').val();   //施主1・名カナ
    //スペースで結合して、施主名(カナ)を作成し、保管
    let mergeName = str1 + ' ' + str2 + '' + str3 + ' ' + str4 + '';
    $p.set($('#Results_Class015'), mergeName);   //施主名マージ
}

//----------------------------------------------------------------------
//ボタン名称変更
$p.changeButtonName = function () {
    //ボタンの特定(②問い合わせ内容管理への新規作成ボタン)
    let siteId = $p.siteId();
    let button = $('button[data-from-site-id="' + siteId + '"][data-to-site-id="335411"].confirm-unload');

    button.each(function () {
        //アイコンのspanはそのまま残し、末尾のテキストードだけ書き換える
        let textNode = $(this).contents().filter(function () {
            return this.nodeType === 3; //テキストノードのみ対象
        }).last();

        if (textNode.length) {
            textNode[0].nodeValue = '新規見積依頼';
        }
    });
}

//----------------------------------------------------------------------
//登録完了(Status=200)以降、マスターシートClassYが選択済みの場合のみ選択変更を不可にする
//未選択のままなら登録完了後も選択可能Pleasanter標準のStatusControls ColumnHashは値の有無で
//条件分岐できないため、こちらでクライアント側から制御する)。
//フィールドコンテナResults_ClassYFieldへ既存の読取り専用CSSクラスcode-readonly2//付け外しする方式335410の01.読み取り専用スタイルで定義済み、pointer-events:none等を適用//tabindexの制御は$p.applyReadonlyTabindexに一本化クラス付与後に呼ばれる想定$p.toggleClassYLock = function () {
    let status = $('#Results_Status').val();
    let classYVal = $('#Results_ClassY').val();
    let statusLocked = ['200', '900', '910', '920'].includes(status);
    let $classYField = $('#Results_ClassYField');

    if (statusLocked && classYVal) {
        $classYField.addClass('code-readonly2');
    } else {
        $classYField.removeClass('code-readonly2');
    }
}

//----------------------------------------------------------------------
//code-readonly2読取り専用が設定されている全フィールドをTab移動対象から除外する。
//pointer-events:noneはマウス操作のみ無効化し、Tabキーでのフォーカス移動・キーボード操作での
//選択変更は防げないため、tabindex="-1"を併用する。静的にExtendedFieldCss: "code-readonly2"が
//設定されている項目Status/ClassA/ClassB/ClassZ等と、ClassYのように動的にクラスが
//付け外しされる項目の両方を、都度まとめて処理する対象外に戻った要素はtabindexも解除$p.applyReadonlyTabindex = function () {
    $('select, input, textarea').each(function () {
        let $el = $(this);
        if ($el.closest('.code-readonly2').length) {
            $el.attr('tabindex', '-1');
        } else if ($el.attr('tabindex') === '-1') {
            $el.removeAttr('tabindex');
        }
    });
}

//----------------------------------------------------------------------
//登録状況Statusが「登録完了(200)」のときだけ依頼発行ボタンを表示、それ以外は非表示にする
$p.toggleNewRequestButton = function () {
    let siteId = $p.siteId();
    let button = $('button[data-from-site-id="' + siteId + '"][data-to-site-id="335411"].confirm-unload');

    let status = $('#Results_Status').val();
    if (status == '200') {
        button.show();
    } else {
        button.hide();
    }
}


$p.mergeUserList = function () {
    //担当者一覧のマージ対象項目(重複除去)
    let users1 = [];
    [
        $('#Results_Class032').val(),   //主担当営業
        $('#Results_Class033').val(),   //副担当営業
        $('#Results_Class034').val(),   //営業設計
        $('#Results_Class035').val(),   //管理設計
        $('#Results_Class036').val(),   //担当IC
        $('#Results_Class037').val(),   //担当監督
        $('#Results_Class038').val(),   //所属長
    ].forEach(function (v) {
        if (v && !users1.includes(v)) users1.push(v);
    });

    let mergeValue = JSON.stringify(users1);   //担当者一覧のマージ対象項目をJSON文字列化
    let class151 = JSON.stringify($('#Results_Class151').val());   //担当者一覧

    //前と内容が同じ場合は上書きしない
    if (mergeValue !== class151) {
        $p.set($('#Results_Class151'), mergeValue);   //担当者一覧
    }

    //CC一覧のマージ対象項目重複除去
    let usersCC = [];
    [
        $('#Results_Class061').val(),   //CC1
        $('#Results_Class062').val(),   //CC2
        $('#Results_Class063').val(),   //CC3
        $('#Results_Class064').val(),   //CC4
    ].forEach(function (v) {
        if (v && !usersCC.includes(v)) usersCC.push(v);
    });

    let mergeValueCC = JSON.stringify(usersCC);   //CC一覧のマージ対象項目をJSON文字列化
    let class152 = JSON.stringify($('#Results_Class152').val());   //CC一覧

    //前と内容が同じ場合は上書きしない
    if (mergeValueCC !== class152) {
        $p.set($('#Results_Class152'), mergeValueCC);   //CC一覧
    }

    //担当者一覧・CC一覧のマージ重複除去
    let usersAll = [];
    users1.concat(usersCC).forEach(function (v) {
        if (v && !usersAll.includes(v)) usersAll.push(v);
    });

    let mergeValueAll = JSON.stringify(usersAll);   //担当者一覧・CC一覧のマージ対象項目をJSON文字列化
    let class153 = JSON.stringify($('#Results_Class153').val());   //担当者・CC一覧

    //前と内容が同じ場合は上書きしない
    if (mergeValueAll !== class153) {
        $p.set($('#Results_Class153'), mergeValueAll);   //担当者・CC一覧
    }

    //登録者(依頼者)担当者・CC一覧をマージしてClass154依頼者・担当者・CC一覧へ保存
    $p.mergeClass154(usersAll);
}

//----------------------------------------------------------------------
//重複を除いてClass154依頼者・担当者・CC一覧、複数選択・非表示へ保存する
$p.mergeClass154 = function (usersAll) {
    let classBVal = $('#Results_ClassB').val();   //登録者(依頼者)

    let merged = [];
    if (classBVal && !merged.includes(classBVal)) merged.push(classBVal);
    usersAll.forEach(function (v) {
        if (v && !merged.includes(v)) merged.push(v);
    });

    let mergeValue = JSON.stringify(merged);
    let class154 = JSON.stringify($('#Results_Class154').val());   //依頼者・担当者・CC一覧

    //前と内容が同じ場合は上書きしない
    if (mergeValue !== class154) {
        $p.set($('#Results_Class154'), mergeValue);   //依頼者・担当者・CC一覧
    }
}

//----------------------------------------------------------------------
//マスターシート・エリア・契約ブランド・契約営業所連携
//
//AutoPostBackLinks設定によるサーバー側自動転記は処理負荷を避けるため全廃し、
//クライアントスクリプトで転記を行う。
//  ・ClassYマスターシート: 選択のたびに$p.apiGetでその場でレコード取得対象レコードが多いため
//  ・Class022/023/031エリア・契約ブランド・契約営業所: BeforeOpeningPageのServerScriptが
//    事前に全件取得しhiddenへ格納済みのデータをクライアント側で逆引きするだけapiGetは呼ばない
//
//上書きルール(共通、重複チェックキーも含め全転記先に適用):
//  ・未選択 → 選択:転記先に値があっても上書きしない(先に手入力していた値を尊重)
//  ・選択済み → 別選択:転記先に値があっても上書きする(マスタを変えたら再転記)
//
//ClassYマスターシート自体は、登録完了(Status=200)以降かつ既に選択済みの場合のみ
//選択変更不可にする(未選択のままなら登録完了後も選択可能にする、$p.toggleClassYLock参照

//ServerScriptが埋め込んだhiddenのマスタデータエリア/契約ブランド/契約営業所)
let areaMasterData = {};
let brandMasterData = {};
let officeMasterData = {};

$p.loadStaticMasters = function () {
    areaMasterData = $('#areaMaster').val() ? JSON.parse($('#areaMaster').val()) : {};
    brandMasterData = $('#brandMaster').val() ? JSON.parse($('#brandMaster').val()) : {};
    officeMasterData = $('#officeMaster').val() ? JSON.parse($('#officeMaster').val()) : {};
}

//変更前値の記憶(未選択→選択/選択済み→別選択の判定に使う)
let previousLookupValues = {
    ClassY: '',
    Class022: '',
    Class023: '',
    Class031: ''
};

$p.rememberLookupBaseValues = function () {
    previousLookupValues.ClassY = $('#Results_ClassY').val();
    previousLookupValues.Class022 = $('#Results_Class022').val();
    previousLookupValues.Class023 = $('#Results_Class023').val();
    previousLookupValues.Class031 = $('#Results_Class031').val();
}

//APIレスポンス$p.apiGet結果から、列名に応じたHash経由で値を取り出す
$p.getApiRecordValue = function (record, fieldName) {
    if (fieldName === 'ResultId' || fieldName === 'Title' || fieldName === 'ItemTitle') {
        return record[fieldName];
    }
    let hashKey =
        fieldName.indexOf('Date') === 0 ? 'DateHash' :
        fieldName.indexOf('Num') === 0 ? 'NumHash' :
        fieldName.indexOf('Description') === 0 ? 'DescriptionHash' :
        fieldName.indexOf('Check') === 0 ? 'CheckHash' :
        fieldName.indexOf('Attachments') === 0 ? 'AttachmentsHash' :
        'ClassHash';
    return record[hashKey] ? record[hashKey][fieldName] : undefined;
}

//転記先1項目へ、上書きルールに従って値をセットする
//wasEmpty=true未選択→選択: 転記先が空の場合のみセット
//wasEmpty=false選択済み→別選択: 常にセット
//戻り値: 実際にセットした場合はtrue呼び出し元で連鎖転記の要否判定に使う
$p.applyLookupValue = function (toColumn, value, wasEmpty) {
    if (value === undefined || value === null || value === '') return false;
    let $target = $('#Results_' + toColumn);
    if (!$target.length) return false;
    if (wasEmpty && $target.val()) return false; //既存の手入力値を保持
    $p.set($target, value);
    return true;
}

//----------------------------------------------------------------------
//ClassYマスターシート、SiteId:189112選択時の転記
//重複チェックキーClass003姓カナ/Class004名カナ/Class022エリアも転記対象に含める。
//上書き可否はキー項目も含め全項目共通のwasEmptyルールに従うapplyLookupValue参照$p.classYFieldMap = [
    { from: 'ClassA', to: 'ClassZ' },
    { from: 'Class001', to: 'Class001' },
    { from: 'Class002', to: 'Class002' },
    { from: 'Class003', to: 'Class003' },
    { from: 'Class004', to: 'Class004' },
    { from: 'Class009', to: 'Class005' },
    { from: 'Class010', to: 'Class006' },
    { from: 'Class011', to: 'Class007' },
    { from: 'Class012', to: 'Class008' },
    { from: 'Class017', to: 'Class009' },
    { from: 'Class018', to: 'Class010' },
    { from: 'Class019', to: 'Class011' },
    { from: 'Class020', to: 'Class012' },
    { from: 'Class029', to: 'Class021' },
    { from: 'ClassW', to: 'Class022' },
    { from: 'Class033', to: 'Class023' },
    { from: 'Class040', to: 'Class024' },
    { from: 'Class043', to: 'Class025' },
    { from: 'ClassF', to: 'Class031' },
    { from: 'ClassG', to: 'Class032' },
    { from: 'ClassH', to: 'Class033' },
    { from: 'ClassI', to: 'Class034' },
    { from: 'ClassL', to: 'Class035' },
    { from: 'ClassK', to: 'Class036' },
    { from: 'ClassJ', to: 'Class037' },
    { from: 'Date063', to: 'Date001' },
    { from: 'DateH', to: 'Date002' }
];

$p.on('change', 'ClassY', function () {
    let newVal = $('#Results_ClassY').val();
    let wasEmpty = !previousLookupValues.ClassY;

    if (newVal) {
        $p.apiGet({
            'id': newVal,
            'async': false,
            'done': function (data) {
                if (!data.Response || !data.Response.Data || !data.Response.Data[0]) {
                    console.log('マスターシートのレコード取得に失敗しました');
                    $p.ex.setMyMessage('new-status-red', 'データの取得に失敗しました。営業積算へお問い合わせください');
                    return;
                }
                //Class022/023/031エリア・契約ブランド・契約営業所がClassY経由で更新された場合、
                //$p.setのtrigger('change')により各項目のchangeハンドラが連鎖発火し、
                //ClassM/ClassN/Class038への転記も自動的に行われる想定
                let record = data.Response.Data[0];
                $p.classYFieldMap.forEach(function (m) {
                    let value = $p.getApiRecordValue(record, m.from);
                    $p.applyLookupValue(m.to, value, wasEmpty);
                });

                //案件状況ClassSを契約コードClassZの転記結果に応じて自動判定
                $p.judgeClassS();
            },
            'fail': function (data) {
                console.log('マスターシートのレコード取得に失敗しました', data);
                $p.ex.setMyMessage('new-status-red', 'データの取得に失敗しました。営業積算へお問い合わせください');
            }
        });
    } else {
        //マスターシート選択解除 → 未契約
        $p.judgeClassS();
    }

    previousLookupValues.ClassY = newVal;
});

//----------------------------------------------------------------------
//案件状況ClassSの自動判定
//マスターシートClassY未選択の場合は未契約。選択済みの場合は、転記された契約コードClassZ//"9999AAABB"(未契約扱いのダミーコード)なら未契約、それ以外なら契約済みと判定する。
//自動判定後もClassS自体は通常項目のまま読取り専用化しないなので、手入力での変更を妨げない。
$p.judgeClassS = function () {
    let classYVal = $('#Results_ClassY').val();
    let classZVal = $('#Results_ClassZ').val();
    let judged = (!classYVal || classZVal === '9999AAABB') ? '0' : '1';
    $p.set($('#Results_ClassS'), judged);
}

//----------------------------------------------------------------------
//Class022エリア、SiteId:210699→ ClassMエリア記号転記
//Class022自身は重複チェックキーのため、この項目自体はロック対象・必須のまま
$p.applyAreaMasterLookup = function (wasEmpty) {
    let newVal = $('#Results_Class022').val();
    if (newVal && areaMasterData[newVal] !== undefined) {
        $p.applyLookupValue('ClassM', areaMasterData[newVal], wasEmpty);
    }
}

$p.on('change', 'Class022', function () {
    let wasEmpty = !previousLookupValues.Class022;
    $p.applyAreaMasterLookup(wasEmpty);
    previousLookupValues.Class022 = $('#Results_Class022').val();
});

//----------------------------------------------------------------------
//Class023契約ブランド、SiteId:109100選択時の転記 → ClassNブランド記号
$p.on('change', 'Class023', function () {
    let newVal = $('#Results_Class023').val();
    let wasEmpty = !previousLookupValues.Class023;

    if (newVal && brandMasterData[newVal] !== undefined) {
        $p.applyLookupValue('ClassN', brandMasterData[newVal], wasEmpty);
    }

    previousLookupValues.Class023 = newVal;
});

//----------------------------------------------------------------------
//Class031契約営業所、SiteId:96243選択時の転記 → Class038所属長
$p.on('change', 'Class031', function () {
    let newVal = $('#Results_Class031').val();
    let wasEmpty = !previousLookupValues.Class031;

    if (newVal && officeMasterData[newVal] !== undefined) {
        $p.applyLookupValue('Class038', officeMasterData[newVal], wasEmpty);
    }

    previousLookupValues.Class031 = newVal;
});

(本ファイルClassYのchangeハンドラ内で「案件状況ClassSを契約コードClassZの転記結果に応じて自動判定」する$p.judgeClassS()呼び出しは、2026-08-03の改修modify/2026-08-03T1520_案件状況自動判定/で追加されたもの。判定はClassY変更時のみ発火するため、判定後の手動変更は次回ClassY操作まで保持される。

scripts/2_02_プロセス処理スクリプト.js.js

/*
@config :
@filename : 02_プロセス処理スクリプト.js
@title : 02_プロセス処理スクリプト.js
@name :
@siteIds :
@siteTitles : プロセス実行前の処理
@disabled: false
*/

//プロセスボタンから呼び出す共通ディスパッチャ(雛形)
//Process定義側のOnClickは $p.ex.processScript($(this)); の形(ボタン要素そのものを渡す形)で呼び出す想定。
//procNum数値文字列ではなく、ボタンの表示ラベル文字列procNameで処理を振り分ける方針②サイトに倣う//プロセス再設計時、switch内にプロセス名ごとのcaseを追加していく。
$p.ex.processScript = async function (target) {
    let $btn = $(target);
    if (!$btn.length) {
        console.log('processScript: 対象ボタンが取得できませんでした');
        return false;
    }

    let procName = $btn.text().replace(/^\s+/, '').trim();
    console.log('processScript ' + procName);

    //確認ダイアログを出さないプロセス名。ここにprocNameを追加すると、そのプロセスはconfirmをスキップして即実行する
    let noConfirmProcesses = [];

    switch (procName) {
        case '登録完了':
            //実行者登録者本人限定、Process側View.ColumnFilterHash: ClassB=Ownで担保
            //要入力チェックありProcess側ValidateInputsで担保
            //確認プロンプトあり
            let popMessage1 = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (!confirm(popMessage1)) return;
            break;
        default:
            let popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (!noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            break;
    }

    //プロセス実行
    $p.execProcess($btn);

    //プロセス側で設定した入力検証ValidateInputsが未入力エラーになった場合、
    //Pleasanterが対象項目へ<label id="Results_XXX-error" class="error" for="Results_XXX">入力必須項目です。</label>を
    //動的追加する。これを拾い、for属性から通常のフィールドラベルclass="error"でない方)を逆引きして
    //まとめて下部にアラート表示する(必須項目名はスクリプト内にハードコードしない、②サイトと同じ方式)
    setTimeout(function () {
        let $errorLabels = $('label.error[id$="-error"]');
        if ($errorLabels.length) {
            let labelTexts = [];
            $errorLabels.each(function () {
                let forAttr = $(this).attr('for');
                let $fieldLabel = $('label[for="' + forAttr + '"]').not('.error').first();
                labelTexts.push($fieldLabel.length ? $fieldLabel.text().trim() : forAttr);
            });
            $p.ex.setMyMessage('new-status-red', labelTexts.join('、') + ' は必須項目です。入力してください。');
        }
    }, 300);
}

10.2 335410 ServerScripts1ファイル

Id Title 実行タイミング 概要
1 マスターデータ取得 (BeforeOpeningPage想定) エリア(210699)・契約ブランド(109100)・契約営業所(96243)マスタを全件取得しHiddenへJSON格納。クライアント側の逆引き転記(10.1節)で使用

serverscripts/1_マスターデータ取得.js

try {
    context.Log('マスターデータ取得(エリア・契約ブランド・契約営業所)開始');
    getAreaMaster();
    getBrandMaster();
    getOfficeMaster();
} catch (error) {
    context.Log(error);
}

//エリアマスタ取得SiteId:210699
//Class022エリア選択時、クライアント側でこのデータを逆引きしてClassMエリア記号へ転記する
function getAreaMaster() {
    let masterTableId = '210699';
    let results = items.Get(masterTableId);
    if (results.Length === 0) {
        context.Log('エリアマスタが見つかりませんでした');
        return;
    }

    let areaMaster = {};
    for (let item of results) {
        areaMaster[item.ResultId] = item.ClassA;
    }

    hidden.Add('areaMaster', JSON.stringify(areaMaster));
    context.Log('エリアマスタを取得しました');
}

//契約ブランドマスタ取得SiteId:109100
//Class023契約ブランド選択時、クライアント側でこのデータを逆引きしてClassNブランド記号へ転記する
function getBrandMaster() {
    let masterTableId = '109100';
    let results = items.Get(masterTableId);
    if (results.Length === 0) {
        context.Log('契約ブランドマスタが見つかりませんでした');
        return;
    }

    let brandMaster = {};
    for (let item of results) {
        brandMaster[item.ResultId] = item.ClassA;
    }

    hidden.Add('brandMaster', JSON.stringify(brandMaster));
    context.Log('契約ブランドマスタを取得しました');
}

//契約営業所マスタ取得SiteId:96243
//Class031契約営業所選択時、クライアント側でこのデータを逆引きしてClass038所属長へ転記する
function getOfficeMaster() {
    let masterTableId = '96243';
    let results = items.Get(masterTableId);
    if (results.Length === 0) {
        context.Log('契約営業所マスタが見つかりませんでした');
        return;
    }

    let officeMaster = {};
    for (let item of results) {
        officeMaster[item.ResultId] = item.ClassK;
    }

    hidden.Add('officeMaster', JSON.stringify(officeMaster));
    context.Log('契約営業所マスタを取得しました');
}

10.3 335411 Scripts4ファイル

Id Title 適用範囲 概要
1 01_基本スクリプト New/Edit 営業積算課/積算発注課判定によるタブ表示制御、①からのリンク経由チェック、祝日計算初期化、各種ボタン生成呼び出し、進捗表示切替、Status⇔Class062同期、依頼内容・ログ類の自動マージ処理、問合せ種別(Class007)による表示・必須・DescriptionCマージ切替、UserId→氏名解決(resolveUserName、2026-08-09追加。ServerScript「Dept25所属者取得スクリプト」がHidden格納したDept25Membersから動的読込、2026-08-09変更)、部署/ユーザー/ブランドのハードコードコードは全てファイル先頭のグローバル変数へ集約(2026-08-09)
2 02_添付ファイルDL New/Edit 添付ファイル項目へのチェックボックス・一括/選択ダウンロードボタン追加、JSZip+axiosによるZIP一括DL
3 03_質疑応答スクリプト New/Edit 質疑・回答メッセージの送受信・履歴表示・削除、各種操作ボタン(質疑送信/回答送信/営業積算確認/受付/外部見積待ちへ/社内確認中へ/一部回答/見積回答/問い合わせ不要/差戻し/遅延連絡)の動的生成、営業日計算。見積回答ファイル一括DLボタンはAttachments101が1件も無い場合は非表示2026-08-09
4 04_プロセス処理スクリプト New/Edit 全19プロセスの共通ディスパッチャ(processScript)、プロセスごとの確認ダイアログ・入力チェック・付随処理の振り分け、問合せ種別必須チェック、日付・ユーザー設定(setTodayAndUser、2026-08-09追加)

scripts/1_01_基本スクリプト.js.js

/*
@config :
@filename : ${psFileBasename}
@title : 01_基本スクリプト
@name :
@siteIds :
@siteTitles : 問合せ内容管理
@disabled: false
*/

let pageLoading = 0;

//組織によって質疑応答の操作可否を分岐するフラグDeptId 25 = 営業積算課)
let eSekisanFlag = false;
let eSekisanDeptId = '25';

//dept25(営業積算課)所属者のUserId→氏名解決マップログ文字列生成・タブ間ミラー表示用のみ//ServerScript「Dept25所属者取得スクリプト」が画面表示前に取得済みのDept25MembersHidden項目から読み込む。
//選択肢自体はChoicesText側でUsersマスタからDeptId=25指定で動的取得している
let dept25UserNames = $('#Dept25Members').length > 0 ? JSON.parse($('#Dept25Members').val()) : {};
//UserIdを氏名に解決する。マップに無い値旧レコードの氏名文字列そのまま等はそのまま返す
function resolveUserName(val) {
    if (!val) return val;
    return dept25UserNames[val] || val;
}

//積算発注タブ_Tab-3/FieldSetTab3表示対象の組織コード25(営業積算課),26(積算発注管理課),28(東京工務課),66(君津住宅工務課),36(愛知工務課)
let sekisanHachuViewDeptIds = ['25', '26', '28', '66', '36'];
//積算発注タブ_Tab-3/FieldSetTab3表示対象の個別ユーザーコード上記部署に属さなくても表示許可する例外ユーザー
//913(青地渉),987(中川真悟),1153(長谷川等志)
let sekisanHachuViewUserIds = ['913', '987', '1153'];
//ブランドコードResults_Class004の値109101=nexthouse, 268122=nexthouse VALUE
//マトリックスの場合のnexthouseシリーズ(Class040)必須判定でのみ使用
let nexthouseBrandCodes = ['109101', '268122'];

//----------------------------------------------------------------------
//初期処理
window.addEventListener('load', function () {
    if (pageLoading == 0) {
        console.log('Window load Event');
        $p.ex.onLoad();
    }
});

$p.events.on_editor_load = function () {
    if (pageLoading == 0) {
        console.log('on Editor Load');
        $p.ex.onLoad();
    }
}

//----------------------------------------------------------------------
$p.ex.onLoad = function () {
    //ロード開始
    pageLoading = 1;

    //初期処理
    $p.ex.init();
}

//初期処理
$p.ex.init = function () {
    console.log('initialize...');

    //営業積算課(DeptId 25)かどうかを判定
    eSekisanFlag = ($p.deptId() == eSekisanDeptId);

    //「営業積算」タブ_Tab-2/FieldSetTab2は営業積算課(Dept25)のみ閲覧・編集可。
    //既定は01_共通スタイル.cssで非表示、Dept25のみここでbodyへクラス付与し表示化する
    if (eSekisanFlag) {
        $('body').addClass('show-sekisan-tab');
    }

    //積算発注タブ_Tab-3/FieldSetTab3表示対象dept25(営業積算),26(積算発注),28,66,36(各エリア工務)またはuserId 913,987,1153
    //sekisanHachuViewDeptIds/sekisanHachuViewUserIdsはファイル先頭のグローバル変数宣言を参照
    let deptIdStr = $p.deptId().toString();
    let userIdStr = $p.userId().toString();

    if (sekisanHachuViewDeptIds.includes(deptIdStr) || sekisanHachuViewUserIds.includes(userIdStr)) {
        $('body').addClass('show-sekisan-hachu-tab');
    }

    //新規作成時のみ処理
    if ($p.action() == 'new') {
        //①物件基本情報からのリンク経由でない場合ClassZ空欄は警告表示保存自体はbefore_validateでブロック
        if (!$('#Results_ClassZ').val()) {
            $p.ex.setMyMessage('new-status-red', '①物件基本情報から新規依頼を作成してください');
        }

        //祝日計算
        $p.getHolidayTable();
    } else {
        //スタートアップメッセージ表示
        //$p.ex.startupMessage();
        $p.setSubNumber();

        //質疑応答送信ボタン生成
        $p.ex.messageSaveButton();

        //受付ボタン生成
        $p.ex.receiptButton();

        //外部見積待ちへボタン生成
        $p.ex.externalEstimateWaitButton();

        //社内確認中へ・問い合わせ不要・差戻しボタン生成
        $p.ex.statusRowButtons();

        //遅延連絡ボタン生成
        $p.ex.delayNoticeButton();

        //一部回答ボタン生成(見積回答ボタンより先に生成し、見積回答をその直下に配置する)
        $p.ex.partialAnswerButton();

        //見積回答ボタン生成
        $p.ex.estimateReplyButton();

        //見積回答ファイル(Attachments101)一括DLボタン生成Description062の下に配置
        $p.ex.estimateAttachmentsDLButton();

        //「①物件基本情報へ戻る」ボタンをコマンド欄の一番左に移動
        $p.ex.moveBackButtonToFirst();

        //質疑応答メッセージ履歴表示
        $p.ex.messageHistory();
    }

    //添付ファイル項目欄調整
    let attachColumns = $('.control-attachments');
    $.each(attachColumns, function (index, value) {
        let attach = value.dataset.name;
        attachmentComments(attach);
        attachFileCheckbox(attach);
    })

    //----------------------------------------------------------------------
    //Results_ClassG(現在の進捗)変更時に項目の表示/非表示を切替
    //ClassGは「新規依頼」プロセス実行後に読取専用化されるため、控え項目ClassT(現在の進捗(控え))へ常に同期しておき、
    //表示切替の判定はClassTを参照する読取専用化後は$('#Results_ClassG').val()が空になるため)
    $('input[name="Results_ClassG"]').on('change', function () {
        $p.toggleClassGFields($(this).val());
        $p.set($('#Results_ClassT'), $(this).val());
    });
    $p.toggleClassGFields($('#Results_ClassT').val());

    //----------------------------------------------------------------------
    //Results_Class007(問合せ種別)変更時にマトリックス以外/マトリックスの入力欄表示を切替
    $('input[name="Results_Class007"]').on('change', function () {
        $p.ex.toggleInquiryTypeFields($(this).val());
    });
    $p.ex.toggleInquiryTypeFields($('#Results_Class007').val());

    //----------------------------------------------------------------------
    //Results_Class055(不備・追加)欄は項目削除に伴い不要化2026-08-01コメントアウト
    /*
    $('input[name="Results_Class055"]').on('change', function () {
        $p.toggleClass055Fields($(this).val());
    });
    $p.toggleClass055Fields($('#Results_Class055').val());
    */

    //アップ希望日チェックイベント登録
    $p.on('change', 'DateA', function () {
        $p.clearMessage();
        $p.upDateCheck();
    });

    //----------------------------------------------------------------------
    //Status(状況)とClass062(状況)の値を相互に同期
    $('#Results_Status').on('change', function () {
        $p.ex.syncStatusAndClass062('Status');
    });
    $('#Results_Class062').on('change', function () {
        $p.ex.syncStatusAndClass062('Class062');
    });

    //code-readonly2読取り専用が設定されている全フィールドをTab移動対象から除外する
    $p.applyReadonlyTabindex();
}

//----------------------------------------------------------------------
//code-readonly2読取り専用が設定されている全フィールドをTab移動対象から除外する。
//pointer-events:noneはマウス操作のみ無効化し、Tabキーでのフォーカス移動・キーボード操作での
//選択変更は防げないため、tabindex="-1"を併用する(①物件基本情報サイトと同じ方式)。
//StatusControls「依頼ロック」によるReadOnly化ExtendedFieldCssとは別のPleasanter標準機構//DOM上の表現が未確認のため対象外。ExtendedFieldCss: "code-readonly2"の静的設定項目のみ対応。
$p.applyReadonlyTabindex = function () {
    $('select, input, textarea').each(function () {
        let $el = $(this);
        if ($el.closest('.code-readonly2').length) {
            $el.attr('tabindex', '-1');
        } else if ($el.attr('tabindex') === '-1') {
            $el.removeAttr('tabindex');
        }
    });
}

//Status変更時はClass062へ、Class062変更時はStatusへ値をコピーする無限ループ防止のため値が異なる場合のみ更新
$p.ex.syncStatusAndClass062 = function (changedField) {
    let statusVal = $('#Results_Status').val();
    let class062Val = $('#Results_Class062').val();

    if (changedField === 'Status') {
        if (class062Val !== statusVal) {
            $p.set($('#Results_Class062'), statusVal);
        }
    } else {
        if (statusVal !== class062Val) {
            $p.set($('#Results_Status'), class062Val);
        }
    }
}

$p.setSubNumber = function () {
    // サブ番号の設定ロジック
    let classB = $('#Results_ClassB').val();
    let classX = $('#Results_ClassX').val();

    let subNumber = classX.split('-')[1]; // 管理番号を「-」で分割して、子番を取得
    if (subNumber && subNumber !== classB) {
        // サブ番号を設定
        $p.set($('#Results_ClassB'), subNumber);
    }
};

//Results_ClassG(現在の進捗)変更時に項目の表示/非表示を切替
$p.toggleClassGFields = function (value) {
    if (value == '1') {
        //表示
        $('#Results_ClassHField').show();
        $('#Results_ClassIField').show();
        $('#Results_DescriptionAField').show();
    } else {
        //非表示
        $('#Results_ClassHField').hide();
        $('#Results_ClassIField').hide();
        $('#Results_DescriptionAField').hide();
    }
}

//----------------------------------------------------------------------
//Results_Class007(問合せ種別)の値に応じて、マトリックス以外/マトリックスそれぞれの入力欄を表示/非表示
//「マトリックス以外」→Class031-034,Description031,Class045,Class050を表示
//「マトリックス」→Class038,Class039,Class040を表示
//未選択の場合は両方非表示
$p.ex.otherTypeFieldNames = ['Class031', 'Class032', 'Class033', 'Class034', 'Description031', 'Class045', 'Class050'];
$p.ex.matrixTypeFieldNames = ['Class038', 'Class039', 'Class040'];
$p.ex.toggleInquiryTypeFields = function (value) {
    if (value == 'マトリックス以外') {
        $p.ex.otherTypeFieldNames.forEach(function (c) { $('#Results_' + c + 'Field').show(); });
        $p.ex.matrixTypeFieldNames.forEach(function (c) { $('#Results_' + c + 'Field').hide(); });
    } else if (value == 'マトリックス') {
        $p.ex.otherTypeFieldNames.forEach(function (c) { $('#Results_' + c + 'Field').hide(); });
        $p.ex.matrixTypeFieldNames.forEach(function (c) { $('#Results_' + c + 'Field').show(); });
    } else {
        $p.ex.otherTypeFieldNames.concat($p.ex.matrixTypeFieldNames).forEach(function (c) { $('#Results_' + c + 'Field').hide(); });
    }
}

//保存時、問合せ種別で選択されなかった側の項目に入力が残っていた場合はクリアする
$p.ex.clearInquiryTypeFields = function () {
    let type = $('#Results_Class007').val();
    let clearTargets;
    if (type == 'マトリックス以外') {
        clearTargets = $p.ex.matrixTypeFieldNames;
    } else if (type == 'マトリックス') {
        clearTargets = $p.ex.otherTypeFieldNames;
    } else {
        clearTargets = $p.ex.otherTypeFieldNames.concat($p.ex.matrixTypeFieldNames);
    }
    clearTargets.forEach(function (c) {
        let $el = $('#Results_' + c);
        if ($el.val()) $p.set($el, '');
    });
}

//問合せ種別に応じた必須項目チェック(プロセス「積算へ依頼」実行前にのみ呼ばれる)
//マトリックスの場合のnexthouseシリーズ(Class040)は、ブランド(Class004)が
//109101(nexthouse)または268122(nexthouse VALUE)のときのみ必須とする
$p.ex.checkInquiryTypeRequiredFields = function () {
    let type = $('#Results_Class007').val();
    let emptyLabels = [];

    if (!type) {
        emptyLabels.push('問合せ種別');
    } else if (type == 'マトリックス以外') {
        if (!$('#Results_Class031').val()) emptyLabels.push('メーカー');
        if (!$('#Results_Class032').val()) emptyLabels.push('商品名');
        if (!$('#Results_Class033').val()) emptyLabels.push('品番');
        if (!$('#Results_Class034').val()) emptyLabels.push('施工範囲・箇所');
    } else if (type == 'マトリックス') {
        if (!$('#Results_Class038').val()) emptyLabels.push('積算Ver.');
        if (!$('#Results_Class039').val()) emptyLabels.push('マトリックスサイズ');

        let class004 = $('#Results_Class004').val();
        if (nexthouseBrandCodes.includes(String(class004)) && !$('#Results_Class040').val()) {
            emptyLabels.push('nexthouseシリーズ');
        }
    }

    if (emptyLabels.length > 0) {
        window.alert(emptyLabels.join('、') + ' は必須項目です。入力してください。');
        return false;
    }
    return true;
}

//Class040(nexthouseシリーズ)のラジオボタン選択を解除する
$p.ex.clearClass040 = function () {
    $('input[name="Results_Class040"]').prop('checked', false);
    $p.set($('#Results_Class040'), '');
}

//Results_Class055(不備・追加)欄は項目削除に伴い不要化2026-08-01コメントアウト
/*
$p.toggleClass055Fields = function (value) {
    if (value == '1') {
        //表示
        $('#Results_Class056Field').show();
        $('#Results_Attachments055Field').show();
        $('#Results_Description055Field').show();
        $('#Attachments055_dlButton1').show(); //添付ファイルのダウンロードボタンを表示
        $('#Attachments055_dlButton2').show(); //添付ファイルのダウンロードボタンを表示
    } else {
        //非表示
        $('#Results_Class056Field').hide();
        $('#Results_Attachments055Field').hide();
        $('#Results_Description055Field').hide();
        $('#Attachments055_dlButton1').hide(); //添付ファイルのダウンロードボタンを非表示
        $('#Attachments055_dlButton2').hide(); //添付ファイルのダウンロードボタンを非表示
    }
}
*/

//入力チェック前処理
$p.events.before_validate = function (args) {

    //ClassZ①物件基本情報からのリンク元IDが空欄の場合、新規作成をブロックする
    if (!$p.ex.checkClassZRequired()) {
        return false;
    }

    //問合せ種別で選択されなかった側の項目をクリア
    $p.ex.clearInquiryTypeFields();

    //依頼内容マージ
    $p.mergeMainMessage();

    //算出対応ログの保存
    $p.saveCalculateLog();

    //遅延連絡ログ
    $p.saveReasonLog();

    //見積回答・別途費用の統合と転記
    $p.mergeEstimateResult();

    //営業積算タブ→問い合わせ情報タブへの転記(同一レコード内)
    $p.syncSekisanTabToGeneralTab();

    //依頼者(ClassC)担当者・CC一覧(Class153)をマージしてClassY依頼者・担当者・CC一覧へ保存
    $p.mergeClassY();

    //ClassG現在の進捗の必須チェックは「新規依頼」プロセス実行前処理04_プロセス処理スクリプト.jsへ移設。
    //新規依頼プロセス実行後はClassG/ClassH/ClassIが読取専用化され$('#Results_ClassH').val()等が取得できなくなるため、
    //通常保存時(本関数)では行わない

    //DescriptionC依頼内容はHide化しmergeMainMessageによる自動集約専用項目になったため、
    //ユーザー入力前提の必須チェックcheckDescriptionCRequiredは廃止
}

//入力チェック後処理
$p.events.after_validate = function (args) {

}

//画面更新後
$p.events.after_set_Update = function (args) {
    //初期処理
    $p.ex.init();
}

//①物件基本情報から新規依頼作成後、遷移元(①)へ戻らず作成した②自身のレコードに留まる設定は
//ClassZ①物件基本情報からのリンク元IDのChoicesText側でNotReturnParentRecordオプションとして
//宣言的に設定するPleasanter標準機能。スクリプト側での対応は不要
//参照: https://pleasanter.org/ja/manual/table-management-choices-text-link


//アップ希望日時の確認と警告
$p.upDateCheck = function () {
    if ($('#Results_DateA').val() != '') {
        let dateA = new Date($('#Results_DateA').val());
        let dateJ = new Date($('#Results_DateJ').val());

        //もしdateJが空の場合 $p.getHolidayTable() を再度実行して、dateJを取得する
        if ($('#Results_DateJ').val() == '') {
            $p.getHolidayTable();
            dateJ = new Date($('#Results_DateJ').val());
        }

        //アップ希望日が期限日より手前の場合
        if (dateA.getTime() < dateJ.getTime()) {
            $('#Results_DateA').addClass('dateA_alert');
            $p.ex.setMyMessage('new-status-red', '期日は選択可最短日以降を選択してください。');

            return false;
        } else {
            $('#Results_DateA').removeClass('dateA_alert');

            return true;
        }
    }
}

//祝日判定テーブルの取得
//ServerScript「祝日リスト取得スクリプト」が画面表示前に取得済みのHolidaysListHidden項目から読み込む
$p.getHolidayTable = function () {
    let holidays = $('#HolidaysList').length > 0 ? JSON.parse($('#HolidaysList').val()) : [];

    let limitDate = new Date();
    limitDate.setHours(0);
    limitDate.setMinutes(0);
    limitDate.setSeconds(0);

    //土日祝日を除く5日後をカウント
    let count = 0;
    while (count < 5) {
        limitDate.setDate(limitDate.getDate() + 1);
        let limitDateStr = limitDate.getFullYear() + '/' + (limitDate.getMonth() + 1).toString().padStart(2, '0') + '/' + limitDate.getDate().toString().padStart(2, '0');
        if (!(limitDate.getDay() == 0) && !(limitDate.getDay() == 6) && !(holidays.includes(limitDateStr))) {
            count++;
        }
    }

    let limitDateStr = limitDate.getFullYear() + '/' + (limitDate.getMonth() + 1).toString().padStart(2, '0') + '/' + limitDate.getDate().toString().padStart(2, '0');
    $p.set($('#Results_DateJ'), limitDateStr);
}


//----------------------------------------------------------------------
//依頼内容マージOK6/21
//問合せ種別(Class007)により、DescriptionCへマージする項目セットを切り替える
//0:マトリックス以外→Class031(メーカー)、Class032(商品名)、Class033(品番)、Class034(施工範囲・箇所)、
//                  Description031(依頼内容補足)、Class045(標準仕様)、Class050(前回の管理番号)
//1:マトリックス→Class038(積算Ver.)、Class039(マトリックスサイズ)、Class040(nexthouseシリーズ)
$p.mergeMainMessage = function (args) {
    //対象項目(種別自体の変更含む)の変更があった場合のみ処理する
    let includeList = ['Results_Class007', 'Results_Class031', 'Results_Class032', 'Results_Class033', 'Results_Class034', 'Results_Description031', 'Results_Class045', 'Results_Class050', 'Results_Class038', 'Results_Class039', 'Results_Class040'];
    let checkFlug = false;
    $.each($p.data.MainForm, function (index, val) {
        if (index.startsWith('Results')) {
            if (includeList.includes(index)) {
                // 処理内容
                checkFlug = true;
            }
        }
    });

    if (checkFlug) {
        let type = $('#Results_Class007').val();
        let mergeStr = [];   //マージする文字列を格納する配列

        if (type == 'マトリックス') {
            mergeStr.push($('#Results_Class038').val());   //Class038(積算Ver.)の値を配列に格納
            mergeStr.push($('#Results_Class039').val());   //Class039(マトリックスサイズ)の値を配列に格納
            mergeStr.push($('#Results_Class040').val());   //Class040(nexthouseシリーズ)の値を配列に格納
        } else {
            mergeStr.push($('#Results_Class031').val());         //Class031(メーカー)の値を配列に格納
            mergeStr.push($('#Results_Class032').val());         //Class032(商品名)の値を配列に格納
            mergeStr.push($('#Results_Class033').val());         //Class033(品番)の値を配列に格納
            mergeStr.push($('#Results_Class034').val());         //Class034(施工範囲・箇所)の値を配列に格納
            mergeStr.push($('#Results_Description031').val());   //Description031(依頼内容補足)の値を配列に格納
            mergeStr.push($('#Results_Class045').val());         //Class045(標準仕様)の値を配列に格納
            mergeStr.push($('#Results_Class050').val());         //Class050(前回の管理番号)の値を配列に格納
        }

        //空欄(未入力)の項目を除外する
        mergeStr = mergeStr.filter(function (val) {
            return val && val.trim() !== '';
        });

        //改行で結合して、DescriptionCに格納
        $p.set($('#Results_DescriptionC'), mergeStr.join('\n'));
    }
}


//----------------------------------------------------------------------
//遅延連絡ログ
//Class086遅延理由、Class087遅延連絡者
//Date084遅延連絡日、Date085返信予定日
//Description081遅延連絡内容
$p.saveReasonLog = function () {
    //対象項目の変更があった場合のみログを保存する
    let includeList = ['Results_Class086', 'Results_Class087', 'Results_Date084', 'Results_Date085', 'Results_Description081'];
    let checkFlug = false;
    $.each($p.data.MainForm, function (index, val) {
        if (index.startsWith('Results')) {
            if (includeList.includes(index)) {
                // 処理内容
                checkFlug = true;
            }
        }
    });

    if (checkFlug) {
        let logStr = [];
        let reasonText = $('#Results_Class086 option:selected').text();   //遅延理由の表示テキストを取得
        logStr.push('遅延理由:' + reasonText);                            //Class086の表示文字列を配列に格納
        logStr.push('遅延連絡者:' + resolveUserName($('#Results_Class087').val()));   //Class087の値を氏名に解決して配列に格納
        logStr.push('遅延連絡日:' + $('#Results_Date084').val());         //Date084の値を配列に格納
        logStr.push('返信予定日:' + $('#Results_Date085').val());         //Date085の値を配列に格納
        logStr.push('遅延連絡内容:' + '\n' + $('#Results_Description081').val());   //Description081の値を配列に格納

        let orgLog = $('#Results_Description083').val();   //Description083の既存の値を取得
        //新しいログを先頭(上)にして、既存のログがある場合はその下に結合。ない場合は新しいログのみ
        let saveLog = orgLog ? logStr.join('\n') + '\n----------------------\n' + orgLog : logStr.join('\n');
        $p.set($('#Results_Description083'), saveLog);   //Description083に保存
    }

}

//算出対応ログ
$p.saveCalculateLog = function () {
    //対象項目の変更があった場合のみログを保存する
    let includeList = ['Results_Class101', 'Results_Class102', 'Results_Date103', 'Results_Class103', 'Results_Description091', 'Results_Description092'];
    let checkFlug = false;
    $.each($p.data.MainForm, function (index, val) {
        if (index.startsWith('Results')) {
            if (includeList.includes(index)) {
                // 処理内容
                checkFlug = true;
            }
        }
    });

    if (checkFlug) {
        //Class101算出者、Class102チェック者、Date103返信日、Class103返信者
        //Description091見積回答、Description092別途費用・伝達事項"
        let logStr = [];
        logStr.push('算出者:' + resolveUserName($('#Results_Class101').val()));      //Class101の値を氏名に解決して配列に格納
        logStr.push('チェック者:' + resolveUserName($('#Results_Class102').val()));  //Class102の値を氏名に解決して配列に格納
        logStr.push('返信日:' + $('#Results_Date103').val());       //Date103の値を配列に格納
        logStr.push('返信者:' + resolveUserName($('#Results_Class103').val()));      //Class103の値を氏名に解決して配列に格納

        //ラベルと内容の間に改行を入れることで、ラベルが独立した行になり、内容は次の行から表示される
        logStr.push('見積回答:' + '\n' + $('#Results_Description091').val());             //Description091の値を配列に格納
        logStr.push('別途費用・伝達事項:' + '\n' + $('#Results_Description092').val());   //Description092の値を配列に格納

        let orgLog = $('#Results_Description101').val();   //Description101の既存の値を取得

        //既存のログがある場合はそれを先頭にして新しいログを結合、ない場合は新しいログのみを結合
        let saveLog = orgLog ? orgLog + '\n----------------------\n' + logStr.join('\n') : logStr.join('\n');
        $p.set($('#Results_Description101'), saveLog);   //Description101に保存
    }
}

//見積回答(Description091)・別途費用・伝達事項(Description092)をDescription093へ統合し、
//Description093の内容をDescription061見積結果へ転記する
$p.mergeEstimateResult = function () {
    //対象項目の変更があった場合のみ処理する
    let includeList = ['Results_Description091', 'Results_Description092'];
    let checkFlug = false;
    $.each($p.data.MainForm, function (index, val) {
        if (index.startsWith('Results')) {
            if (includeList.includes(index)) {
                checkFlug = true;
            }
        }
    });

    if (checkFlug) {
        //091・092を改行で結合してDescription093へ格納内容に変更が無ければ上書きしない
        let mergeStr = $('#Results_Description091').val() + '\n' + $('#Results_Description092').val();
        if ($('#Results_Description093').val() !== mergeStr) {
            $p.set($('#Results_Description093'), mergeStr);
        }

        //Description093をDescription061へ転記内容に変更が無ければ上書きしない
        if ($('#Results_Description061').val() !== mergeStr) {
            $p.set($('#Results_Description061'), mergeStr);
        }
    }
}

//----------------------------------------------------------------------
//ClassZ①物件基本情報からのリンク元IDが新規作成時に空欄の場合、新規作成をブロックする
//(①物件基本情報のリンクを経由せず直接このサイトで新規作成しようとした異常系を検知)
$p.ex.checkClassZRequired = function () {
    if ($p.action() == 'new' && !$('#Results_ClassZ').val()) {
        $p.ex.setMyMessage('new-status-red', '①物件基本情報から新規依頼を作成してください');
        return false;
    }
    return true;
}

//----------------------------------------------------------------------
//営業積算タブ_Tab-2→問い合わせ情報タブGeneralへの転記同一レコード内
//受付日Date081→Date061、受付者Class081→Class061
//見積依頼日Date082→Date062、見積依頼先Class083→Class063
//見積受信日Date090→Date063、見積種別Class091→Class065
//転記先の値に差異がある場合のみ転記し、差異が無い場合はスキップする
$p.syncSekisanTabToGeneralTab = function () {
    let mapping = [
        { from: 'Class081', to: 'Class061', resolveUser: true },   //受付者Usersマスタ参照のため氏名に解決して転記
        { from: 'Class083', to: 'Class063' },   //見積依頼先
        { from: 'Date081', to: 'Date061' },     //受付日
        { from: 'Date082', to: 'Date062' },     //見積依頼日
        { from: 'Date090', to: 'Date063' },     //見積受信日
        { from: 'Class091', to: 'Class065' },   //見積種別
    ];

    mapping.forEach(function (item) {
        let fromVal = $('#Results_' + item.from).val();
        if (item.resolveUser) fromVal = resolveUserName(fromVal);
        let toVal = $('#Results_' + item.to).val();

        //値に差異がある場合のみ転記する
        if (fromVal !== toVal) {
            $p.set($('#Results_' + item.to), fromVal);
        }
    });
}

//----------------------------------------------------------------------
//依頼者(ClassC単一選択)と担当者・CC一覧(Class153複数選択)をマージし、
//重複を除いてClassY依頼者・担当者・CC一覧、複数選択・非表示へ保存する
$p.mergeClassY = function () {
    let classCVal = $('#Results_ClassC').val();
    let class153Vals = $('#Results_Class153').val() || [];

    let merged = [];
    if (classCVal) merged.push(classCVal);
    class153Vals.forEach(function (v) {
        if (v && !merged.includes(v)) merged.push(v);
    });

    $p.set($('#Results_ClassY'), JSON.stringify(merged));
}

//----------------------------------------------------------------------
//返信予定日 Date085 変更イベントを検知して実行
$p.on('change', 'Date085', function () {
    if ($('#Results_Date085').val() != '') {
        console.log('返信予定日が変更されました。');
        $p.replaceDate085();
    }
});

//Description082には「直前に本文へ置換した返信予定日の値」をキーとして保持する
//(レコード保存・再読込後も同じ値が残るため、次回変更時に古い値だけを新しい値へ置換できる)
$p.replaceDate085 = function () {
    if ($('#Results_Date085').val() != '') {
        console.log('返信予定日が変更されました。');
        //返信予定日(今回の新しい値)を取得
        let date085 = $('#Results_Date085').val();

        //遅延理由メッセージを取得
        let content081 = $('#Results_Description081').val();
        //前回置換に使った返信予定日キーをDescription082から取得
        let content082 = $('#Results_Description082').val();

        let newContent;
        if (content082 == '') {
            //初回:プレースホルダー(%返信予定日%)を返信予定日に置換
            newContent = content081.replace(/%返信予定日%/g, date085);
        } else {
            //2回目以降前回の返信予定日キーを今回の返信予定日に置換
            newContent = content081.split(content082).join(date085);
        }

        //遅延理由を再設定する
        $p.set($('#Results_Description081'), newContent);

        //今回の返信予定日を次回のキーとしてDescription082に保存
        $p.set($('#Results_Description082'), date085);
    }
}

//----------------------------------------------------------------------
//日付文字列を「年/月/日(曜日)」形式に整形する(例:"2026/06/25" → "2026/6/25(木)"
function formatDateWithWeekday(dateStr) {
    if (!dateStr) return '';
    let datePart = dateStr.split(' ')[0];   //時刻が付いている場合は日付部分のみ取り出す
    let parts = datePart.split('/');
    if (parts.length < 3) return datePart;   //想定外の形式の場合はそのまま返す

    let year = parseInt(parts[0], 10);
    let month = parseInt(parts[1], 10);
    let day = parseInt(parts[2], 10);
    let dateObj = new Date(year, month - 1, day);

    let weekdays = ['日', '月', '火', '水', '木', '金', '土'];
    let weekday = weekdays[dateObj.getDay()];

    return year + '/' + month + '/' + day + '(' + weekday + ')';   //例2026/6/25(木)
}

scripts/2_02_添付ファイルDL.js.js

/*
@config :
@filename : 02_添付ファイルDL.js
@title : 02_添付ファイルDL
@name :
@siteIds :
@siteTitles : 問合せ内容管理
@disabled: false
*/

/*
init内部
let attachColumns = $('.control-attachments');
$.each(attachColumns, function (index, value) {
    let attach = value.dataset.name;
    attachmentComments(attach);
    attachFileCheckbox(attach);
})
*/

//----------------------------------------------------------------------
//添付ファイル項目にボタンの追加
function attachFileCheckbox(attach) {
    //管理番号 Results_ClassX
    let title = $('#Results_ClassX').val();

    let colName = $('#Results_' + attach + 'Field label')[0].innerHTML;
    let attachData = JSON.parse($p.getControl(attach).val());
    let zipFilename = title + '■' + colName + '■';

    //console.log(attachData.length);
    if (attachData.length >= 1) {
        $.each(attachData, function (index, value) {
            let checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.style.margin = '2px';
            checkbox.style.float = 'left';
            checkbox.name = attach + '_checkbox';
            checkbox.id = value.Guid + '_checkbox';
            checkbox.value = value.Guid;

            $('#' + value.Guid).prepend(checkbox);

        });

        //ダウンロードボタン
        let div = document.createElement('div');
        let dlButton1 = document.createElement('button');
        dlButton1.type = 'button';
        dlButton1.id = attach + '_dlButton1';
        dlButton1.style.marginRight = '5px';
        dlButton1.innerHTML = '全ファイルDL';
        dlButton1.setAttribute('attach', attach);
        dlButton1.setAttribute('onclick', 'dlAllFiles("' + attach + '","' + zipFilename + '全ファイルDL' + '")');

        let dlButton2 = document.createElement('button');
        dlButton2.type = 'button';
        dlButton2.id = attach + '_dlButton2';
        dlButton2.style.marginRight = '5px';
        dlButton2.innerHTML = '選択DL';
        dlButton2.setAttribute('attach', attach);
        dlButton2.setAttribute('onclick', 'dlSelectedFiles("' + attach + '","' + zipFilename + '選択DL' + '")');

        div.style.margin = '5px';
        div.style.marginTop = '0px';
        div.style.marginLeft = '120px';
        div.style.marginRight = '5px';
        div.style.marginBottom = '10px';
        div.style.clear = 'both';
        div.append(dlButton1);
        div.append(dlButton2);
        $('#Results_' + attach + 'Field').after(div);
        $('#' + attach + '_dlButton1').button({
            icon: 'ui-icon-document'
        });
        $('#' + attach + '_dlButton2').button({
            icon: 'ui-icon-document'
        });
    }
}

function dlSelectedFiles(attach, zipFilename) {
    //console.log(zipFilename);
    //チェックがついているもののみ
    const fileList = $('input[name="' + attach + '_checkbox"]:checked');
    let guidList = new Array();
    for (item of fileList) {
        //console.log(item);
        guidList.push(item.value);
    }

    if (guidList.length > 0) {
        dlZipAttachements(guidList, zipFilename);
    }

}

function dlAllFiles(attach, zipFilename) {
    let guidList = new Array();
    //全部
    const fileList = JSON.parse($p.getControl(attach).val());
    for (item of fileList) {
        guidList.push(item.Guid);
    }

    if (guidList.length > 0) {
        dlZipAttachements(guidList, zipFilename);
    }
}

/**
 * 複数ファイルを1ファイルにzip圧縮してDLする
 * @param  guidList DLする添付ファイルのGUIDのリスト
 * @param  zipFilename zipファイル名
 * @return なし
 */
async function dlZipAttachements(guidList, zipFilename) {
    var binary;
    var filename;
    const zip = new JSZip();
    const jsonReq = {
        ApiVersion: 1.1
    };

    for (guid of guidList) {
        await axios.post("/pleasanter/api/binaries/" + guid + "/get", jsonReq)
            .then(function (response) {
                binary = decodeBase64(response.data.Response.Base64);
                filename = response.data.Response.FileName;
                zip.folder(zipFilename).file(filename, binary, {
                    binary: true
                });
            })
            .catch(function (error) {
                console.log(error);
            })
            .finally(function () { });
    }
    saveAs(await zip.generateAsync({
        type: "blob"
    }), zipFilename + ".zip");
}

/**
 * base64デコード処理
 * @param  base64 base64文字列
 * @return デコードしたバイナリデータ
 */
function decodeBase64(base64) {
    const bin = atob(base64.replace(/^.*,/, ''));
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
    }
    return buffer.buffer;
}

//説明欄にコメントがある場合、追加
function attachmentComments(attach) {
    let comments = '';

    if ($('#Results_' + attach + 'Field>div.field-control').attr('title')) {
        comments = $('#Results_' + attach + 'Field>div.field-control').attr('title');
    }

    if (comments) {
        let div1 = document.createElement('div');
        div1.innerHTML = comments;
        div1.style.marginLeft = '121px';
        div1.style.fontWeight = 'bold';
        div1.style.color = '#00F';

        //位置調整とコメント追加
        $('#Results_' + attach + 'Field').append(div1);
        $('#Results_' + attach + 'Field').css('margin-bottom', '10px');
    }

}

scripts/3_03_質疑応答スクリプト.js.js

/*
@config :
@filename : 03_質疑応答スクリプト.js
@title : 03_質疑応答スクリプト
@name :
@siteIds :
@siteTitles : 問合せ内容管理
@disabled: false
*/

//----------------------------------------------------------------------
//質疑応答機能configs\参考ソース\■3_310405_Re③依頼項目\sites\scripts\03_質疑応答スクリプト.js を
//本サイト335411の既存予約項目Description075/076/071、Num071/072、Class071/075/077、Date073/074、
//Attachments071/072に合わせて移植・拡張したもの。
//
//項目対応(参照元 → 本サイト)
//  Description131(質疑入力欄)   → Description075「質疑内容」
//  Description132(回答入力欄)   → Description076「回答内容」
//  Description135(ログ保存用)   → Description071「質疑回答データ保存用」
//  Num131/132(質疑回数/回答回数) → Num071/Num072
//  Class131(質疑回答ステータス0-3) → Class071選択肢の値・色分けとも参照元と一致
//  Attachments131/132           → Attachments071/072
//
//本サイトにのみ存在し参照元スクリプトにはなかった項目は、下記の想定で組み込んでいます(要確認)。
//  Class075「質疑回答送信者」   :質疑または回答を送信した本人の氏名を、送信のたびに上書き記録
//  Class077「質疑送付先」       :質疑送信時に選択した宛先ユーザーを、ログの表示にのみ利用(通知メール送信は未実装)
//  Date073「質疑回答期日」      :質疑送信時に、$p.getHolidayTable()と同じ祝日データ(HolidaysList)を使って
//                                 土日祝を除く5営業日後を自動計算して設定
//  Date074「営業積算確認日」        :営業積算課が回答内容を確認した際(営業積算確認プロセス実行時)に本日日付を設定
//
//回答送信ボタンの表示自体はProcess「回答送信」側のView条件(ClassC:["Own"])でサーバー側に強制されている
//ClassC=依頼者本人のみ実行可。processScript側にも念のため同じ判定を残している
//processScript本体は04_プロセス処理スクリプト.jsへ統合済み。プロセスボタン共通ディスパッチャは
//  1ファイルに1つしか定義できないため、19プロセス全件を扱う04側へ集約し、本ファイルには
//  saveQuestionMessage等の実処理関数のみを残す構成にしている//----------------------------------------------------------------------

//営業積算課からの質疑を保存
$p.ex.saveQuestionMessage = function () {

    let message = $('#Results_Description075').val();
    let userName = $p.userName();
    let now = new Date();
    let nowStr = now.getFullYear() + '/' + (now.getMonth() + 1).toString().padStart(2, '0') + '/' + now.getDate().toString().padStart(2, '0') + ' ' +
        now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0') + ':' + now.getSeconds().toString().padStart(2, '0');

    let messageID = 'a' + now.getFullYear() + '' + (now.getMonth() + 1).toString().padStart(2, '0') + '' + now.getDate().toString().padStart(2, '0') + '' +
        now.getHours().toString().padStart(2, '0') + '' + now.getMinutes().toString().padStart(2, '0') + '' + now.getSeconds().toString().padStart(2, '0') + '-' + $p.userId();
    let num = parseFloat($('#Results_Num071').val() || 0) + 1;

    //質疑送付先Class077複数ユーザー選択、要確認表示形式によってはセレクタの調整が必要
    let sendToText = $('#Results_Class077').val() || '';

    let data = {
        'messageID': messageID,
        'type': 'Question',
        'num': toCircledNumber(num),
        'typeStr': '営業積算課からの質疑',
        'userName': userName,
        'sendTo': sendToText,
        'messageTime': nowStr,
        'messageBody': message,
        'enabled': true,
    };

    //質疑回答 保管用項目に保存する
    let saveData = $('#Results_Description071').val();
    let shitsugiData;

    if (saveData == '') {
        shitsugiData = [];
        shitsugiData.push(data);
    } else {
        shitsugiData = JSON.parse(saveData);
        shitsugiData.push(data);
    }
    $p.set($('#Results_Description071'), JSON.stringify(shitsugiData));

    //送信者を記録
    $p.set($('#Results_Class075'), userName);

    //質疑回数をカウントアップ
    $p.set($('#Results_Num071'), num);

    //質疑回答ステータスを「質疑あり」に更新
    $p.set($('#Results_Class071'), '1');

    //質疑回答期日土日祝を除く5営業日後を設定
    $p.set($('#Results_Date073'), $p.ex.calcBusinessDeadline(5));

    //質疑ありチェックをオンにする
    $p.set($('#Results_Check071'), 'checked');

    //送信したら入力欄は削除する
    $p.set($('#Results_Description075'), '');
}

//依頼者からの回答を保存
$p.ex.saveAnswerMessage = function () {

    let message = $('#Results_Description076').val();
    let userName = $p.userName();
    let now = new Date();
    let nowStr = now.getFullYear() + '/' + (now.getMonth() + 1).toString().padStart(2, '0') + '/' + now.getDate().toString().padStart(2, '0') + ' ' +
        now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0') + ':' + now.getSeconds().toString().padStart(2, '0');

    let messageID = 'b' + now.getFullYear() + '' + (now.getMonth() + 1).toString().padStart(2, '0') + '' + now.getDate().toString().padStart(2, '0') + '' +
        now.getHours().toString().padStart(2, '0') + '' + now.getMinutes().toString().padStart(2, '0') + '' + now.getSeconds().toString().padStart(2, '0') + '-' + $p.userId();
    let num = parseFloat($('#Results_Num072').val() || 0) + 1;

    let data = {
        'messageID': messageID,
        'type': 'Answer',
        'num': toCircledNumber(num),
        'typeStr': '依頼者からの回答',
        'userName': userName,
        'messageTime': nowStr,
        'messageBody': message,
        'enabled': true,
    };

    //質疑回答 保管用項目に保存する
    let saveData = $('#Results_Description071').val();
    let shitsugiData;

    if (saveData == '') {
        shitsugiData = [];
        shitsugiData.push(data);
    } else {
        shitsugiData = JSON.parse(saveData);
        shitsugiData.push(data);
    }
    $p.set($('#Results_Description071'), JSON.stringify(shitsugiData));

    //送信者を記録
    $p.set($('#Results_Class075'), userName);

    //回答回数をカウントアップ
    $p.set($('#Results_Num072'), num);

    //質疑回答ステータスを「回答あり」に更新
    $p.set($('#Results_Class071'), '2');

    //回答ありチェックをオンにする
    $p.set($('#Results_Check072'), 'checked');

    //送信したら入力欄は削除する
    $p.set($('#Results_Description076'), '');
}

//営業積算課が回答内容を確認
$p.ex.confirmAnswerMessage = function () {
    let now = new Date();
    let nowStr = now.getFullYear() + '/' + (now.getMonth() + 1).toString().padStart(2, '0') + '/' + now.getDate().toString().padStart(2, '0');

    //営業積算確認日を設定
    $p.set($('#Results_Date074'), nowStr);

    //質疑回答ステータスを「営業積算確認済み」に更新
    $p.set($('#Results_Class071'), '3');
}

//質疑回答メッセージ履歴の表示
$p.ex.messageHistory = function () {
    //質疑回数と回答回数の参照
    let num071 = parseFloat($('#Results_Num071').val());
    let num072 = parseFloat($('#Results_Num072').val());

    if (num071 > 0 || num072 > 0) {
        //タブの色の変更要確認本サイトでは質疑応答セクションは_Tab-1FieldSetTab1に配置
        let shitsugi_Status = $('#Results_Class071').val();

        switch (shitsugi_Status) {
            case '': //質疑なし
            case '0': //質疑なし
                $('a[href="#FieldSetTab1"] label').text('質疑なし');
                $('a[href="#FieldSetTab1"]').css('background-color', 'blue');
                $('a[href="#FieldSetTab1"]').css('color', 'white');
                break;
            case '1': //質疑あり
                $('a[href="#FieldSetTab1"] label').text('質疑あり');
                $('a[href="#FieldSetTab1"]').css('background-color', 'red');
                $('a[href="#FieldSetTab1"]').css('color', 'white');
                break;
            case '2': //回答あり
                $('a[href="#FieldSetTab1"] label').text('回答あり');
                $('a[href="#FieldSetTab1"]').css('background-color', 'yellow');
                $('a[href="#FieldSetTab1"]').css('color', 'black');
                break;
            case '3': //営業積算確認済み
                $('a[href="#FieldSetTab1"] label').text('営業積算確認済み');
                $('a[href="#FieldSetTab1"]').css('background-color', 'green');
                $('a[href="#FieldSetTab1"]').css('color', 'white');
                break;
        }

        //メッセージテーブルがすでに存在したら削除
        if ($('#messageTable').length) {
            $('#messageTable').remove();
        }

        //削除ボタン削除
        if ($('#messageDelete').length) {
            $('#messageDelete').remove();
        }

        let saveData = $('#Results_Description071').val();

        if (saveData) {
            //メッセージデータの取得
            let messageData = JSON.parse(saveData);

            let templateHtml =
                `<table id="messageTable" class="radius-table">
    <tbody>
        {{#messageData}}
        <tr class="{{type}}">
            <td class="shitsugi_type">{{typeStr}} {{num}}</td>
            <td class="shitsugi_userName">{{userName}}</td>
            <td class="shitsugi_messageTime">{{messageTime}}</td>
        </tr>
        <tr class="{{type}}">
            <td colspan="2" class="shitsugi_messageBody">
                <pre class="shitsugi_preWrap">{{{messageBody}}}</pre>
            </td>
            <td class="shitsugi_deleteCheck"><input type="checkbox" name="messageDeleteCheck" value="{{messageID}}"></input></td>
        </tr>
        {{/messageData}}
    </tbody>
</table>`;

            let template = Hogan.compile(templateHtml);
            let outputHtml = template.render({
                "messageData": messageData
            });

            //質疑内容入力欄の直上にメッセージ履歴テーブルを表示する
            //本サイトにはSectionFields番号を直接指定できる根拠がないため、既知の項目=Description075Fieldを起点に挿入
            setTimeout(function () {
                $('#SectionFields8').append(outputHtml);
            }, 300);
        }

        //対応するプロセスボタン「質疑回答削除」が実在する場合のみ削除ボタンを表示Depts・View条件にそのまま追従
        if ($p.ex.processButtonExists('質疑回答削除')) {
            let button3 = document.createElement('button');
            button3.type = 'button';
            button3.id = 'messageDelete';
            button3.innerHTML = '質疑回答削除';
            button3.setAttribute('onclick', "$p.ex.clickProcessByName('質疑回答削除')");
            button3.style.margin = '5px';
            button3.style.float = 'right';
            button3.style.fontSize = '1.3em';

            setTimeout(function () {
                $('#Results_Description075Field').before(button3);
                $('#messageDelete').button({
                    icon: 'ui-icon-document'
                });
            }, 300);
        }
    }
}

//質疑・回答送信ボタン作成
$p.ex.messageSaveButton = function () {
    //対応するプロセスボタン「質疑送信」が実在する場合のみ表示Depts・View条件による表示制御にそのまま追従
    if ($p.ex.processButtonExists('質疑送信') && !$('#saveQuestion').length) {
        let div1 = document.createElement('div');
        div1.style.textAlign = 'right';
        let button1 = document.createElement('button');
        button1.type = 'button';
        button1.id = 'saveQuestion';
        button1.innerHTML = '質疑送信';
        button1.setAttribute('onclick', "$p.ex.clickProcessByName('質疑送信')");
        button1.style.margin = '5px';
        button1.style.fontSize = '1.3em';
        div1.append(button1);
        $('#Results_Attachments071Field').after(div1);
        $('#saveQuestion').button({
            icon: 'ui-icon-document'
        });
    } else {
        //営業積算課以外は質疑入力欄・添付欄を非表示
        $('#Results_Description075Field').hide();
        $('#Results_Attachments071Field').hide();
    }

    //対応するプロセスボタン「積算対応中へ」が実在する場合のみ表示Depts・View条件による表示制御にそのまま追従
    //(ボタンのラベル「営業積算確認」は維持。実行先プロセスは新プロセス仕様で「積算対応中へ」(Id:6)に統合されたため、
    //  clickProcessByNameの対象名のみ変更。confirmAnswerMessage()は04_プロセス処理スクリプト.jsのcase'積算対応中へ'内で呼ぶ)
    if ($p.ex.processButtonExists('積算対応中へ') && !$('#confirmAnswer').length) {
        let div4 = document.createElement('div');
        div4.style.textAlign = 'right';
        let button4 = document.createElement('button');
        button4.type = 'button';
        button4.id = 'confirmAnswer';
        button4.innerHTML = '営業積算確認';
        button4.setAttribute('onclick', "$p.ex.clickProcessByName('積算対応中へ')");
        button4.style.margin = '5px';
        button4.style.fontSize = '1.3em';
        div4.append(button4);
        $('#Results_Attachments072Field').after(div4);
        $('#confirmAnswer').button({
            icon: 'ui-icon-document'
        });
    }

    //対応するプロセスボタン「回答送信」が実在する場合のみ表示
    //Process「回答送信」側のView条件(ClassC:["Own"])による表示制御にそのまま追従。userId直接比較は行わない
    if ($p.ex.processButtonExists('回答送信') && !$('#saveAnswer').length) {
        let div2 = document.createElement('div');
        div2.style.textAlign = 'right';
        let button2 = document.createElement('button');
        button2.type = 'button';
        button2.id = 'saveAnswer';
        button2.innerHTML = '回答送信';
        button2.setAttribute('onclick', "$p.ex.clickProcessByName('回答送信')");
        button2.style.margin = '5px';
        button2.style.fontSize = '1.3em';
        div2.append(button2);
        $('#Results_Attachments072Field').after(div2);
        $('#saveAnswer').button({
            icon: 'ui-icon-document'
        });
    } else {
        //依頼者本人以外は回答入力欄・添付欄を非表示
        $('#Results_Description076Field').hide();
        $('#Results_Attachments072Field').hide();
    }

}

//遅延連絡ボタン作成対応するプロセスボタン「遅延連絡」が実在する場合のみ表示。Depts・View条件にそのまま追従
$p.ex.delayNoticeButton = function () {
    if ($p.ex.processButtonExists('遅延連絡') && !$('#sendDelayNotice').length) {
        let div5 = document.createElement('div');
        div5.style.textAlign = 'right';
        div5.style.float = 'right';
        let button5 = document.createElement('button');
        button5.type = 'button';
        button5.id = 'sendDelayNotice';
        button5.innerHTML = '遅延連絡';
        button5.setAttribute('onclick', "$p.ex.clickProcessByName('遅延連絡')");
        button5.style.margin = '0px 10px 5px 5px';
        button5.style.fontSize = '1.3em';
        div5.append(button5);
        $('#Results_Description081Field').after(div5);
        $('#sendDelayNotice').button({
            icon: 'ui-icon-mail-closed'
        });
    }
}

//受付ボタン作成(受付者(Class081)欄の右横に配置。対応するプロセスボタン「受付」が実在する場合のみ表示)
$p.ex.receiptButton = function () {
    if ($p.ex.processButtonExists('受付') && !$('#sendReceipt').length) {
        let div = document.createElement('div');
        div.style.textAlign = 'right';
        div.style.float = 'right';
        let button = document.createElement('button');
        button.type = 'button';
        button.id = 'sendReceipt';
        button.innerHTML = '受付';
        button.setAttribute('onclick', "$p.ex.clickProcessByName('受付')");
        button.style.margin = '0px 10px 5px 5px';
        button.style.fontSize = '1.3em';
        div.append(button);
        $('#Results_Class081Field').after(div);
        $('#sendReceipt').button({
            icon: 'ui-icon-mail-closed'
        });
    }
}

//外部見積待ちへボタン作成(見積依頼者(Class082)欄の右横に配置)
$p.ex.externalEstimateWaitButton = function () {
    if ($p.ex.processButtonExists('外部見積待ちへ') && !$('#sendExternalEstimateWait').length) {
        let div = document.createElement('div');
        div.style.textAlign = 'right';
        div.style.float = 'right';
        let button = document.createElement('button');
        button.type = 'button';
        button.id = 'sendExternalEstimateWait';
        button.innerHTML = '外部見積待ちへ';
        button.setAttribute('onclick', "$p.ex.clickProcessByName('外部見積待ちへ')");
        button.style.margin = '0px 10px 5px 5px';
        button.style.fontSize = '1.3em';
        div.append(button);
        $('#Results_Class082Field').after(div);
        $('#sendExternalEstimateWait').button({
            icon: 'ui-icon-mail-closed'
        });
    }
}

//社内確認中へ・問い合わせ不要・差戻しボタン作成(状況(Status)欄の右横に一列で配置)
$p.ex.statusRowButtons = function () {
    if ($('#sendInternalConfirmRow').length) {
        return; //二重生成防止
    }
    let defs = [
        { id: 'sendInternalConfirm', label: '社内確認中へ', process: '社内確認中へ' },
        { id: 'sendInquiryUnnecessary', label: '問い合わせ不要', process: '問い合わせ不要' },
        { id: 'sendReturnRequest', label: '差戻し', process: '差戻し' },
    ];
    let visibleDefs = defs.filter(function (d) { return $p.ex.processButtonExists(d.process); });
    if (visibleDefs.length === 0) {
        return;
    }

    let div = document.createElement('div');
    div.id = 'sendInternalConfirmRow';
    div.style.textAlign = 'right';
    div.style.float = 'right';
    visibleDefs.forEach(function (d) {
        let button = document.createElement('button');
        button.type = 'button';
        button.id = d.id;
        button.innerHTML = d.label;
        button.setAttribute('onclick', "$p.ex.clickProcessByName('" + d.process + "')");
        button.style.margin = '0px 10px 5px 5px';
        button.style.fontSize = '1.3em';
        div.append(button);
    });
    $('#Results_StatusField').after(div);
    visibleDefs.forEach(function (d) {
        $('#' + d.id).button({
            icon: 'ui-icon-mail-closed'
        });
    });
}

//一部回答・見積回答ボタン作成(返信者(Class103)欄の右横に、一部回答を上段・見積回答を下段で縦に配置)
//いずれも対応するプロセスボタンが実在する場合のみ表示。Depts・View条件にそのまま追従。
//活性/非活性のクライアント側制御は行わず、実行可否もProcess側のView条件(Status)にのみ委ねる
$p.ex.partialAnswerButton = function () {
    if ($p.ex.processButtonExists('一部回答') && !$('#sendPartialAnswer').length) {
        let div = document.createElement('div');
        div.style.textAlign = 'right';
        div.style.float = 'right';
        let button = document.createElement('button');
        button.type = 'button';
        button.id = 'sendPartialAnswer';
        button.innerHTML = '一部回答';
        button.setAttribute('onclick', "$p.ex.clickProcessByName('一部回答')");
        button.style.margin = '0px 10px 5px 5px';
        button.style.fontSize = '1.3em';
        div.append(button);
        $('#Results_Class103Field').after(div);
        $('#sendPartialAnswer').button({
            icon: 'ui-icon-mail-closed'
        });
    }
}

$p.ex.estimateReplyButton = function () {
    if ($p.ex.processButtonExists('見積回答') && !$('#sendEstimateReply').length) {
        let div6 = document.createElement('div');
        div6.style.textAlign = 'right';
        div6.style.float = 'right';
        let button6 = document.createElement('button');
        button6.type = 'button';
        button6.id = 'sendEstimateReply';
        button6.innerHTML = '見積回答';
        button6.setAttribute('onclick', "$p.ex.clickProcessByName('見積回答')");
        button6.style.margin = '0px 10px 5px 5px';
        button6.style.fontSize = '1.3em';
        div6.append(button6);
        //一部回答ボタン(#sendPartialAnswer)が生成済みならその直後、無ければClass103Fieldの直後に配置し、
        //一部回答の真下(画面上は右寄せの縦並びで一部回答が上段)になるようにする
        let $anchor = $('#sendPartialAnswer').closest('div');
        if ($anchor.length) {
            $anchor.after(div6);
        } else {
            $('#Results_Class103Field').after(div6);
        }
        $('#sendEstimateReply').button({
            icon: 'ui-icon-mail-closed'
        });
    }
}

//見積回答ボタン押下時、営業積算タブ添付ファイル(Attachments101)を問い合わせタブ(Description062)へ転記する
//単一項目からの転記のため「■■■項目名■■■」の見出しは付けない
$p.ex.transcribeEstimateAttachments = function () {
    let attachData = [];
    try {
        attachData = JSON.parse($p.getControl('Attachments101').val());
    } catch (e) {
        attachData = [];
    }

    let lines = attachData.map(function (file) {
        return `▷ [${file.Name}](/pleasanter/binaries/${file.Guid}/show) [▶DL](/pleasanter/binaries/${file.Guid}/download)`;
    });

    $p.set($('#Results_Description062'), lines.join('  \n'));
}

//Description062の下に、見積回答ファイル(Attachments101)一括DLボタンを配置する
//DLファイル名は「親番子番_見積回答ファイル」。Attachments101が1件も無い場合は生成しない
$p.ex.estimateAttachmentsDLButton = function () {
    if ($('#estimateAttachmentsDL').length) {
        return; //二重生成防止
    }

    let attachData = [];
    try {
        attachData = JSON.parse($p.getControl('Attachments101').val());
    } catch (e) {
        attachData = [];
    }
    if (!attachData.length) {
        return; //添付ファイルが1件も無い場合は表示しない
    }

    let classA = $('#Results_ClassA').val(); //親番
    let classB = $('#Results_ClassB').val(); //子番
    let zipFilename = classA + classB + '_見積回答ファイル';

    let div = document.createElement('div');
    div.style.margin = '5px';
    div.style.float = 'right';
    let button = document.createElement('button');
    button.type = 'button';
    button.id = 'estimateAttachmentsDL';
    button.innerHTML = '一括DL';
    button.setAttribute('onclick', 'dlAllFiles("Attachments101","' + zipFilename + '")');
    div.append(button);
    $('#Results_Description062Field').after(div);
    $('#estimateAttachmentsDL').button({
        icon: 'ui-icon-disk'
    });
}

//「①物件基本情報へ戻る」ボタンをコマンド欄(#MainCommandsの一番左に移動する
//Processes側のId構成に依存しないよう、ボタンラベル文字列で対象を特定する
$p.ex.moveBackButtonToFirst = function () {
    let $backButton = $('button[id^="Process_"]').filter(function () {
        return $(this).text().replace(/^\s+/, '').trim() === '①物件基本情報へ戻る';
    });
    if ($backButton.length) {
        $('#MainCommands').prepend($backButton);
    }
}

//質疑回答メッセージの削除(チェックの入った履歴を無効化)
$p.ex.messageDelete = function () {
    let messageData = JSON.parse($('#Results_Description071').val());

    $('input[name="messageDeleteCheck"]:checked').each(function (index) {
        let messageID = $(this).val();
        messageData = messageData.map(item => {
            if (item.messageID === messageID) {
                return {
                    ...item,
                    enabled: false,
                    type: item.type + '.Disabled'
                };
            }
            return item;
        });
    });
    $p.set($('#Results_Description071'), JSON.stringify(messageData));

    $p.ex.setMyMessage('new-status-green', '選択した質疑回答メッセージを削除しました');
}

//ボタンラベル文字列に一致する、現在表示されているプロセスボタンをクリックする
//Processes側のId構成・件数に依存しないようにするための共通ヘルパー
$p.ex.clickProcessByName = function (name) {
    let target = $('button[id^="Process_"]').filter(function () {
        return $(this).text().replace(/^\s+/, '').trim() === name;
    }).first();

    if (target.length) {
        target.click();
    } else {
        $p.ex.setMyMessage('new-status-red', '現在の状態では「' + name + '」を実行できません');
    }
}

//ボタンラベル文字列に一致する、現在表示されているプロセスボタンが実在するかどうかを判定する
//Depts・View条件によるプロセスボタンの表示制御にそのまま追従させるため。deptコード等の直接判定はしない
$p.ex.processButtonExists = function (name) {
    return $('button[id^="Process_"]').filter(function () {
        return $(this).text().replace(/^\s+/, '').trim() === name;
    }).length > 0;
}

//processScriptプロセスボタン共通ディスパッチャは04_プロセス処理スクリプト.jsへ統合済み。
//本ファイルには実処理関数saveQuestionMessage等のみ残す。

//質疑回答期日を計算本日からholidays(#HolidaysList)を除いたN営業日後
$p.ex.calcBusinessDeadline = function (businessDays) {
    let holidays = $('#HolidaysList').length > 0 ? JSON.parse($('#HolidaysList').val()) : [];

    let limitDate = new Date();
    limitDate.setHours(0);
    limitDate.setMinutes(0);
    limitDate.setSeconds(0);

    let count = 0;
    while (count < businessDays) {
        limitDate.setDate(limitDate.getDate() + 1);
        let limitDateStr = limitDate.getFullYear() + '/' + (limitDate.getMonth() + 1).toString().padStart(2, '0') + '/' + limitDate.getDate().toString().padStart(2, '0');
        if (!(limitDate.getDay() == 0) && !(limitDate.getDay() == 6) && !(holidays.includes(limitDateStr))) {
            count++;
        }
    }

    return limitDate.getFullYear() + '/' + (limitDate.getMonth() + 1).toString().padStart(2, '0') + '/' + limitDate.getDate().toString().padStart(2, '0');
}

// numを丸数字に変換して表示
function toCircledNumber(n) {
    n = Math.floor(Number(n));
    if (isNaN(n) || n < 0 || n > 100) return n;
    if (n === 0) return '⓪';
    if (n <= 20) return String.fromCharCode(9311 + n); // 9312 = ①
    if (n <= 35) return String.fromCharCode(12881 + n - 21); // 12881 = ㉑
    if (n <= 50) return String.fromCharCode(12977 + n - 36); // 12977 = ㊱
    if (n <= 100) return n; // fallback for 51-100
}

scripts/4_04_プロセス処理スクリプト.js.js

/*
@config :
@filename : 04_プロセス処理スクリプト.js
@title : 04_プロセス処理スクリプト.js
@name :
@siteIds :
@siteTitles : プロセス実行前の処理
@disabled: false
*/

//プロセスボタンから呼び出す共通ディスパッチャ
//Process定義側のOnClickは $p.ex.processScript($(this)); の形(ボタン要素そのものを渡す形)。
//procNum数値文字列ではなく、ボタンの表示ラベル文字列procNameで処理を振り分ける
//Processes側のId構成や件数が今後変わっても影響を受けないため。
//  3_03_質疑応答スクリプト.jsに元々あったprocessScript本体をこちらへ統合済み
$p.ex.processScript = async function (target) {
    let $btn = $(target);
    if (!$btn.length) {
        console.log('processScript: 対象ボタンが取得できませんでした');
        return false;
    }

    let procName = $btn.text().replace(/^\s+/, '').trim();
    console.log('processScript ' + procName);

    let popMessage = '';

    //確認ダイアログを出さないプロセス名。ここにprocNameを追加すると、そのプロセスはconfirmをスキップして即実行する
    let noConfirmProcesses = [];

    // プロセス名に応じた処理popMessageが未定義(空文字)、またはnoConfirmProcessesに含まれる場合は確認メッセージを出さず実行
    switch (procName) {
        case '積算へ依頼':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            //ClassG現在の進捗が「1:承認図後・着工中」の場合の必須項目チェックプロセス実行後はClassG/H/Iが読取専用化されるため、実行前のここでのみチェック可能
            if (!$p.ex.checkClassGRequiredFields()) return;
            //問合せ種別(マトリックス以外/マトリックス)に応じた必須項目チェック
            if (!$p.ex.checkInquiryTypeRequiredFields()) return;
            break;
        case '受付':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            //受付日・受付者をここで設定Process側の「データの変更」だと反映がexecProcess後(サーバー側)になり、
            //先に動く01_基本スクリプト.jsのbefore_validate/ログ転記処理が値取得できず欠落するため、ここで直接設定する)
            $p.ex.setTodayAndUser('Date081', 'Class081');
            break;
        case '質疑送信':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            if ($('#Results_Description075').val()) {
                $p.ex.saveQuestionMessage();
            } else {
                $p.ex.setMyMessage('new-status-red', '質疑内容を入力してください');
                return false;
            }
            break;
        case '回答送信': //念のためのボタン非表示以外のサーバー側判定はProcess側View条件(ClassC:["Own"])が担う
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            if ($p.userId().toString() != $('#Results_ClassC').val()) {
                $p.ex.setMyMessage('new-status-red', '回答送信は依頼者本人のみ行えます');
                return false;
            }
            if ($('#Results_Description076').val()) {
                $p.ex.saveAnswerMessage();
            } else {
                $p.ex.setMyMessage('new-status-red', '回答内容を入力してください');
                return false;
            }
            break;
        case '質疑回答削除':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            if ($('input[name="messageDeleteCheck"]:checked').length) {
                $p.ex.messageDelete();
            } else {
                $p.ex.setMyMessage('new-status-red', '削除したい質疑回答メッセージを選択してください');
                return false;
            }
            break;
        case '積算対応中へ':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            //旧「営業積算確認」プロセスを吸収。営業積算確認日(Date074)・質疑回答ステータス(Class071='3')を更新
            $p.ex.confirmAnswerMessage();
            break;
        case '外部見積待ちへ':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            //見積依頼日・見積依頼者をここで設定(理由は「受付」と同じ)
            $p.ex.setTodayAndUser('Date082', 'Class082');
            break;
        case '社内確認中へ':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            break;
        case '一部回答':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            //返信日・返信者をここで設定(理由は「受付」と同じ)
            $p.ex.setTodayAndUser('Date103', 'Class103');
            break;
        case '見積回答':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            //実行可否はProcess側View条件(Status)のみで担保しているため、追加の入力チェックは行わない
            //返信日・返信者をここで設定(理由は「受付」と同じ)
            $p.ex.setTodayAndUser('Date103', 'Class103');
            //営業積算タブ添付ファイル(Attachments101)を問い合わせタブ(Description062)へ転記
            $p.ex.transcribeEstimateAttachments();
            break;
        case '完了':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            break;
        case '問い合わせ不要':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            break;
        case '依頼修正':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            break;
        case '差戻し':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            break;
        case '依頼取消':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            break;
        case '保留':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            break;
        case '保留解除':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            break;
        case '遅延連絡':
            popMessage = '"' + procName + '" 処理を実行します。よろしいですか?';
            if (popMessage && !noConfirmProcesses.includes(procName) && !confirm(popMessage)) return;
            if (!$('#Results_Description081').val()) {
                $p.ex.setMyMessage('new-status-red', '遅延連絡内容を入力してください');
                return false;
            }
            //遅延連絡日・遅延連絡者をここで設定理由は「受付」と同じ。saveReasonLogが直後に読み取るため必須
            $p.ex.setTodayAndUser('Date084', 'Class087');
            break;
        case '①物件基本情報へ戻る':
            //Status変更を伴わないため execProcess を経由せず直接画面遷移する
            let parentId = $('#Results_ClassZ').val();
            //自分のURL+parentIdへ遷移 自分のURLの例:https://neo999.next-hd.net/pleasanter/items/335482
            let currentUrl = window.location.href;
            let baseUrl = currentUrl.split('/items/')[0];
            let targetUrl = baseUrl + '/items/' + parentId;
            window.location.href = targetUrl;
            break;
        default:
            console.log('processScript: 未対応のプロセス名です: ' + procName);
            return false;
    }

    // プロセス実行
    $p.execProcess($btn);

    //プロセス側で設定した入力検証ValidateInputsが未入力エラーになった場合、
    //Pleasanterが対象項目へ<label id="Results_XXX-error" class="error" for="Results_XXX">入力必須項目です。</label>を
    //動的追加する。これを拾い、for属性から通常のフィールドラベルclass="error"でない方)を逆引きして
    //まとめて下部にアラート表示する(必須項目名はスクリプト内にハードコードしない)
    setTimeout(function () {
        let $errorLabels = $('label.error[id$="-error"]');
        if ($errorLabels.length) {
            let labelTexts = [];
            $errorLabels.each(function () {
                let forAttr = $(this).attr('for');
                let $fieldLabel = $('label[for="' + forAttr + '"]').not('.error').first();
                labelTexts.push($fieldLabel.length ? $fieldLabel.text().trim() : forAttr);
            });
            $p.ex.setMyMessage('new-status-red', labelTexts.join('、') + ' は必須項目です。入力してください。');
        }
    }, 300);

}

//----------------------------------------------------------------------
//日付項目(dateColumn)に本日日付、ユーザー項目(userColumn)にログインユーザーを設定する。
//以前はProcess側の「データの変更」機能で設定していたが、その反映サーバー側はexecProcess後になり、
//先に動くbefore_validate側のログ転記処理saveReasonLog/saveCalculateLog等が値を取得できず欠落する
//不具合があったため、execProcess呼び出し前にクライアント側で直接設定するここへ移設した
$p.ex.setTodayAndUser = function (dateColumn, userColumn) {
    let now = new Date();
    let dateStr = now.getFullYear() + '/' + (now.getMonth() + 1).toString().padStart(2, '0') + '/' + now.getDate().toString().padStart(2, '0');
    $p.set($('#Results_' + dateColumn), dateStr);
    $p.set($('#Results_' + userColumn), $p.userId());
}

//----------------------------------------------------------------------
//ClassG現在の進捗が「1:承認図後・着工中」の場合、ClassH現場の進捗・ClassI必要な見積金額//DescriptionA問い合わせ経緯が未入力ならアラート表示プロセス実行自体をブロックする
//「新規依頼」プロセス実行前のみ呼ばれる新規依頼以降はStatusControlsによりClassG/H/Iが読取専用化されるため、01_基本スクリプト.jsのbefore_validateからここへ移設
$p.ex.checkClassGRequiredFields = function () {
    if ($('#Results_ClassG').val() == '1') {
        let emptyLabels = [];
        if (!$('#Results_ClassH').val()) emptyLabels.push('現場の進捗');
        if (!$('#Results_ClassI').val()) emptyLabels.push('必要な見積金額');
        if (!$('#Results_DescriptionA').val()) emptyLabels.push('問い合わせ経緯');

        if (emptyLabels.length > 0) {
            window.alert(emptyLabels.join('、') + ' は必須項目です。入力してください。');
            return false;
        }
    }
    return true;
}

10.4 335411 ServerScripts3ファイル

Id Title 実行タイミング 概要
0 子番自動保存スクリプト 新規作成直後(サーバー側) ClassX(自動採番)確定後に子番ClassBを算出しDB保存。クライアント側$p.setSubNumberと同一ロジックをサーバー側にも実装編集画面を開かないとClassBが保存されない不具合対策
1 祝日リスト取得スクリプト (BeforeOpeningPage想定) 祝日テーブル(SiteId 192267)から本日-30日〜+60日分を取得しHidden格納。あわせて独自ボタンで代替済みの標準プロセスボタン6件を非表示化
2 Dept25所属者取得スクリプト BeforeOpeningPage depts.Get('25').GetMembers()でDept25(営業積算課)所属者のUserId→氏名マップを取得しHidden項目Dept25Membersへ格納。クライアント側resolveUserNameが読み込み参照従来の固定ハードコードマップを置換、2026-08-09新規追加

serverscripts/0_子番自動保存スクリプト.js.js

/*
@config :
@filename : 子番自動保存スクリプト.js
@title : 子番自動保存スクリプト
@name :
@siteIds :
@siteTitles : 問合せ内容管理
@disabled: false
*/

try {
    setSubNumber();
} catch (error) {
    context.Log(error);
}

//新規作成直後(自動採番=ClassX確定後にClassXから子番ClassBを算出しDBへ保存する。
//クライアント側 $p.setSubNumberscripts/1_01_基本スクリプト.js.jsと同じロジック・同じガードをサーバー側にも実装し、
//「編集画面を開かない限りClassBがDBへ保存されない」不具合に対応する。
function setSubNumber() {
    let classX = model.ClassX;
    if (!classX) {
        context.Log('[子番自動保存] ClassXが空のためスキップしました。ResultId=' + model.ResultId);
        return;
    }

    let subNumber = classX.split('-')[1]; //管理番号を「-」で分割して子番を取得
    if (subNumber && subNumber !== model.ClassB) {
        model.ClassB = subNumber;
        model.UpdateOnExit = true; //スクリプト終了時に現在のレコード情報を自動保存
        context.Log('[子番自動保存] ClassBを保存しました。ResultId=' + model.ResultId + ' ClassB=' + subNumber);
    }
}

serverscripts/1_祝日リスト取得スクリプト.js.js

/*
@config :
@filename : 祝日リスト取得スクリプト.js
@title : 祝日リスト取得スクリプト
@name :
@siteIds :
@siteTitles : 問合せ内容管理
@disabled: false
*/

try {
    getHolidayList();

    //サイト内に個別設置した独自ボタン(質疑送信・回答送信・質疑回答削除・営業積算確認・受付・外部見積待ちへ・
    //社内確認中へ・一部回答・見積回答・問い合わせ不要・差戻し・遅延連絡、いずれも3_03_質疑応答スクリプト.jsの
    //clickProcessByNameで対応プロセスを呼び出すで代替しているプロセスボタンは非表示にする。
    //それ以外のプロセスボタンはPleasanter標準の表示制御Depts・View条件に委ね、ここでは操作しない
    elements.DisplayType('Process_2', 3);   //受付
    elements.DisplayType('Process_3', 3);   //質疑送信
    elements.DisplayType('Process_4', 3);   //回答送信
    elements.DisplayType('Process_5', 3);   //質疑回答削除
    elements.DisplayType('Process_6', 3);   //積算対応中へ(営業積算確認ボタンで代替)
    elements.DisplayType('Process_7', 3);   //外部見積待ちへ
    elements.DisplayType('Process_8', 3);   //社内確認中へ
    elements.DisplayType('Process_9', 3);   //一部回答
    elements.DisplayType('Process_10', 3);  //見積回答
    elements.DisplayType('Process_12', 3);  //問い合わせ不要
    elements.DisplayType('Process_14', 3);  //差戻し
    elements.DisplayType('Process_18', 3);  //遅延連絡

} catch (error) {
    context.Log(error);
}

//祝日判定テーブルの取得本日時点から手前30日先60日分をHiddenに格納
function getHolidayList() {
    let today = new Date();
    let startDate = new Date();
    let endDate = new Date();

    //30日前の日付を計算
    startDate.setDate(today.getDate() - 30);
    //60日後の日付を計算
    endDate.setDate(today.getDate() + 60);

    let startDateStr = formatDate(startDate) + ' 00:00:00';
    let endDateStr = formatDate(endDate) + ' 23:59:59';
    let filterDate = '["' + startDateStr + ',' + endDateStr + '"]';

    let data = {
        'View': {
            'ColumnFilterHash': {
                'DateA': filterDate,
            },
            'ColumnSorterHash': {
                'DateA': 'asc',
            }
        }
    };

    //祝日テーブルSiteId: 192267からデータを取得
    let results = items.Get(192267, JSON.stringify(data));
    context.Log(`取得した祝日の件数: ${results.Length}`);

    let holidays = [];
    for (let item of results) {
        holidays.push(formatDate(new Date(item.DateA)));
    }

    //取得した祝日日付リストをHiddenに格納
    hidden.Add('HolidaysList', JSON.stringify(holidays));
}

//日付をYYYY/MM/DD形式に整形
function formatDate(date) {
    return date.getFullYear() + '/' +
        (date.getMonth() + 1).toString().padStart(2, '0') + '/' +
        date.getDate().toString().padStart(2, '0');
}

serverscripts/2_Dept25所属者取得スクリプト.js.js

/*
@config :
@filename : Dept25所属者取得スクリプト.js
@title : Dept25所属者取得スクリプト
@name :
@siteIds :
@siteTitles : 問合せ内容管理
@disabled: false
*/

try {
    getDept25Members();
} catch (error) {
    context.Log(error);
}

//Dept25(営業積算課)所属者のUserId→氏名マップを取得しHiddenへ格納する。
//クライアント側resolveUserName()がここから読み込み、表示専用の氏名解決に使う
function getDept25Members() {
    let myDept = depts.Get('25');
    let map = {};

    if (myDept) {
        let members = myDept.GetMembers();
        for (let m of members) {
            map[m.UserId] = m.Name;
        }
    }

    hidden.Add('Dept25Members', JSON.stringify(map));
}

dept.GetMembers()はPleasanter公式マニュアルサーバースクリプトでの部署メンバー取得に基づく実装。実機テストで動作確認済み2026-08-09、Dept25所属3名=647/737/912のUserId・Nameを正しく取得。members.Lengthは大文字プロパティが存在せずundefinedだが、for...ofでの反復自体は正常動作)。

11. その他設定

設定項目 335410 335411
AutoVerUpType 3 2
AllowCopy falseコピー機能無効 未設定(既定=有効)
HideLink true true
NoDisplayIfReadOnly false false
NotInheritPermissionsWhenCreatingSite false false
TabLatestId 1追加タブなし 4
SectionLatestId 9 18
Comments(コメント機能) ラベル「変更履歴」として使用 ラベル未設定EditorColumnHashにも非掲載実質未使用
Htmls外部ライブラリ読込 設定なし あり(下記)

335411 Htmls外部ライブラリ、PositionType:9000, New/Edit両方に適用

<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.18.1/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>

10.3節 scripts/2_02_添付ファイルDL.js.jsのZIP一括ダウンロード機能JSZip/axios/saveAsが依存する外部CDNライブラリ。335410にはこの読込設定がなく、335410側スクリプトに同種のZIP DL機能はない。

12. 気になる点(レビュー観点)

  • 335411 Description093 が Columns 未定義10.3節のスクリプト(mergeEstimateResult)は$('#Results_Description093')へ値をセットしているが、SiteSettings.ColumnsにもEditorColumnHashにもDescription093は存在しない。編集画面上に該当DOM要素が存在しないため、このセット処理は実質的に空振りno-opになっている可能性が高い。既存のDescription061(見積結果)への転記は別行で行われているため実害はなさそうだが、デッドコード/設計意図と実装の乖離として要確認。
  • 335411 Class064採用状況が編集画面のどのタブにも配置されていないColumns定義Hide:true、選択肢採用/不採用/打合せ中はあるがEditorColumnHash全タブを走査しても出現しない。スクリプトからの参照も無し。未使用列の疑い。
  • 335411 Date071質疑回答送信日時が事実上未使用_Tab-1にHide項目として配置されてはいるが、スクリプト内で値をセットしている箇所が見当たらないDate073質疑回答期日、Date074回答確認日は使われているが、Date071自体への代入処理なし
  • 335411 Date091使用期限がEditorColumnHashに存在しないExtendedHtmlAfterControl付きのColumns定義はあるが、どのタブにも配置されておらず画面上には出現しない。類似ラベル・同一文言のExtendedHtmlAfterControlを持つDate064Hide:trueとの重複・リネーム痕跡の可能性があるどちらか一方が旧項目の残骸と推測
  • 335411 ClassZ のラベルが実態と乖離ラベルは「テスト・リンク用」だが、実際には①335410との連携キー15項目Lookup、AutoPostBack)として本番稼働中の中核列。命名がテスト目的のまま放置されている。
  • 335411 Tab-3「積算発注」はReadColumnAccessControls未設定Create/UpdateColumnAccessControlsはDept25/26限定だが、Read閲覧側の部署制限はAPIレベルでは設定されていない。UI上はCSS(show-sekisan-hachu-tabクラス)でタブごと非表示にしているのみのため、API直接呼び出し等でCSSによる制御を経由しない経路からは対象部署外でも閲覧できる可能性がある335410のClass199同様、Read側も明示的に絞る設計との一貫性という観点で要検討
  • PermissionType値の意味は環境依存5.1節のPermissionForCreating/Updating値4、2章の各Permissions511/7/3等は、いずれもビットフラグの加算方式のため本仕様書単体では正確な権限範囲を断定できない。環境のロール定義と突き合わせが必要。
  • 335411 Class050「前回の管理番号」の入力方式説明文は「該当の番号を選択してください」と選択式を示唆する文言だが、ChoicesTextが設定されておらず実際は自由入力のテキスト項目。ガイド文と実装のUI種別が一致していない。
  • 335411 Description061にExtendedFieldCssExtendedControlCssが両方code-readonly2設定他のreadonly項目はExtendedFieldCssのみが一般的で、本列のみ二重指定になっている(実害は無いと見られるが実装の非対称箇所として記録)。
  • 335410↔335411間で同名だが用途の異なる列名が複数存在:例えばClassYは335410で「マスターシート」Lookup選択列、335411では「依頼者・担当者・CC一覧」Hide集約列と全く異なる用途。Class153は両サイトとも「担当者・CC一覧」の同一用途で連携されているが、ClassZ335410=契約コード335411=①へのリンクキー)、ClassS335410のみ存在等、列名の対応関係は個別に本仕様書4章・7章で都度確認する必要がある。横断的な命名規則は存在しない。
  • 335411 StatusControls「依頼ロック」ColumnHashにClassF削除済み列が残存2026-08-09時点でClassF事前相談者はColumns/EditorColumnHashから既に削除されているが、StatusControls「依頼ロック」(Id:1)のColumnHashには"ClassF":"ReadOnly"が残ったまま今回の改修で新たに追加したClass007と合わせて、削除漏れとして残置を確認。存在しない列を指定しているだけで実害はないと見られるが、整理対象として記録。
  • 335411 ClassZのNotReturnParentRecordはJSON形式Lookupへの類推適用:公式マニュアル(table-management-choices-text-link)はシンプル記法[[100,NotReturnParentRecord]]のみ明記しており、ClassZのようなLookups付きJSON形式ChoicesTextへ"NotReturnParentRecord": trueを追加する形式Priority設定と同じ配置パターンから類推が同じ効果を持つかは未確認。実機で①からの新規依頼作成→②の新規レコードに留まるかの確認が必要2026-08-09追加