お家マッチング
PROPERTY MATCHING SYSTEM
パスワードが違います
担当者にパスワードをお問い合わせください
📊 EXCEL
待機中
🔗 SHEETS
未設定
自動マッチング
タップして同期
自動マッチング
0
買いニーズ
0
売り物件
マッチ候補
0
通知送信済

自動マッチング

買いニーズと売り物件を物件種別・エリア・価格帯で照合します

80点以上
🏢

「マッチング実行」ボタンを押すか
データを同期すると照合結果が表示されます

買主ニーズリスト

登録されている買いニーズ一覧

0件
氏名 / 会社 連絡先 希望エリア 予算 物件種別 希望面積 / 条件

売り物件リスト

登録されている売り物件一覧

0件
登録者 / 会社 物件種別 物件名 / 所在地 価格 土地面積 建物面積 築年 備考 資料

成約リスト

成立した取引の記録

🤝

成約データはまだありません

連携・通知 設定

Google スプレッドシート連携・LINE通知・メール通知を設定します

🔗 Google スプレッドシート連携(Google Apps Script)
GAS WEB APP URL
自動同期間隔(分)

① スプレッドシートを開き「拡張機能 → Apps Script」を選択
② 以下のコードを全て貼り付けて保存 → 「デプロイ → 新しいデプロイ」→ 種類: ウェブアプリ → アクセス: 全員
③ 発行されたURLを上の「GAS WEB APP URL」に設定 → スクリプトプロパティに LINE_CHANNEL_ACCESS_TOKENLINE_RECIPIENT_ID を追加

Code.gs — Google Apps Script
// =====================================================
//  JCI大阪不動産クラブ — GAS連携スクリプト (LINE Messaging API版)
//  【スクリプトプロパティに設定する値】
//    LINE_CHANNEL_ACCESS_TOKEN : チャンネルアクセストークン(長期)
//    LINE_RECIPIENT_ID         : 送信先グループID(下記で自動取得)
//    SCORE_THRESHOLD           : 通知スコア閾値(例: 60)
// =====================================================

function doGet(e) {
  try {
    const ss          = SpreadsheetApp.getActiveSpreadsheet();
    const buyerSheet  = ss.getSheetByName('買主リスト');
    const sellerSheet = ss.getSheetByName('売主リスト');
    const buyers      = buyerSheet  ? parseSheet(buyerSheet,  'buyer')  : [];
    const sellers     = sellerSheet ? parseSheet(sellerSheet, 'seller') : [];
    return jsonOut({ buyers, sellers,
      updated: Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss'),
      counts: { buyers: buyers.length, sellers: sellers.length } });
  } catch(err) { return jsonOut({ error: err.message }); }
}

function doPost(e) {
  try {
    const body = JSON.parse(e.postData.contents);
    // LINEウェブフックイベント → グループIDを自動保存
    if (body.events) { captureRecipientId(body.events); return jsonOut({ ok: true }); }
    // HTMLからの通知リクエスト → LINE送信
    if (body.message) return jsonOut(pushLineMessage(body.message));
    return jsonOut({ error: '不明なリクエスト' });
  } catch(err) { return jsonOut({ success: false, error: err.message }); }
}

// LINE Messaging API でプッシュ送信
function pushLineMessage(text) {
  const props = PropertiesService.getScriptProperties();
  const token = props.getProperty('LINE_CHANNEL_ACCESS_TOKEN');
  const toId  = props.getProperty('LINE_RECIPIENT_ID');
  if (!token) return { success: false, error: 'LINE_CHANNEL_ACCESS_TOKEN未設定' };
  if (!toId)  return { success: false, error: 'LINE_RECIPIENT_ID未設定(グループにボットを招待してメッセージを送ってください)' };
  const res = UrlFetchApp.fetch('https://api.line.me/v2/bot/message/push', {
    method: 'post', contentType: 'application/json',
    headers: { Authorization: 'Bearer ' + token },
    payload: JSON.stringify({ to: toId, messages: [{ type: 'text', text: text }] }),
    muteHttpExceptions: true
  });
  const status = res.getResponseCode();
  return status === 200 ? { success: true } : { success: false, lineStatus: status, lineError: res.getContentText() };
}

// ウェブフックでグループIDを自動取得・保存
function captureRecipientId(events) {
  const props = PropertiesService.getScriptProperties();
  if (props.getProperty('LINE_RECIPIENT_ID')) return;
  for (const ev of events) {
    const id = ev.source && (ev.source.groupId || ev.source.roomId || ev.source.userId);
    if (id) { props.setProperty('LINE_RECIPIENT_ID', id); break; }
  }
}

// スプレッドシート変更時トリガー(トリガー登録: onSheetEdit / 変更時)
function onSheetEdit() {
  const props     = PropertiesService.getScriptProperties();
  const threshold = parseInt(props.getProperty('SCORE_THRESHOLD') || '60');
  if (!props.getProperty('LINE_CHANNEL_ACCESS_TOKEN')) return;
  const ss      = SpreadsheetApp.getActiveSpreadsheet();
  const buyers  = parseSheet(ss.getSheetByName('買主リスト'),  'buyer');
  const sellers = parseSheet(ss.getSheetByName('売主リスト'), 'seller');
  const highs   = [];
  for (const b of buyers) for (const s of sellers) {
    const sc = quickScore(b, s);
    if (sc >= threshold) highs.push({ buyer: b, seller: s, score: sc });
  }
  if (!highs.length) return;
  // 重複排除
  const key      = 'sent_pairs_cache';
  const sent     = new Set(JSON.parse(props.getProperty(key) || '[]'));
  const newHighs = highs.filter(r => !sent.has(pairKey(r)));
  if (!newHighs.length) return;
  const msg = '【JCI大阪不動産クラブ】\n📊 スプレッドシート更新\n高スコアマッチング ' + newHighs.length + '件検出!\n\n' +
    newHighs.slice(0,5).map(r => '●' + r.score + '点\n買主: ' + r.buyer.company +
      '\nエリア: ' + r.buyer.area + '\n売主: ' + r.seller.company +
      '\n物件: ' + r.seller.location + ' / ' + r.seller.price).join('\n\n') +
    (newHighs.length > 5 ? '\n\n…他' + (newHighs.length - 5) + '件' : '');
  pushLineMessage(msg);
  newHighs.forEach(r => sent.add(pairKey(r)));
  props.setProperty(key, JSON.stringify([...sent].slice(-500)));
}

function pairKey(r) {
  return [r.buyer.company,r.buyer.area,r.buyer.type,r.seller.company,r.seller.location].join('|');
}

function parseSheet(sheet, mode) {
  if (!sheet) return [];
  const rows = sheet.getDataRange().getValues();
  if (rows.length < 2) return [];

  // ヘッダー行を自動検出(1〜3行目を候補として検索)
  const fmt = v => !v && v !== 0 ? '' : v instanceof Date
    ? Utilities.formatDate(v,'Asia/Tokyo','yyyy/MM/dd') : String(v).trim();
  const KEY_BUYER  = ['日付','氏名','会社','連絡','エリア','予算','種別','面積','その他','備考','条件','希望'];
  const KEY_SELLER = ['日付','氏名','会社','連絡','態様','種別','物件','所在','価格','土地','建物','築','その他','備考'];
  const keys = mode === 'buyer' ? KEY_BUYER : KEY_SELLER;
  let headerRow = 0;
  let bestScore = 0;
  for (let i = 0; i < Math.min(4, rows.length); i++) {
    const score = rows[i].filter(h => keys.some(k => String(h).includes(k))).length;
    if (score > bestScore) { bestScore = score; headerRow = i; }
  }

  const headers = rows[headerRow].map(h => String(h).trim());
  const col = (...candidates) => {
    for (const k of candidates) {
      const idx = headers.findIndex(h => h.includes(k));
      if (idx >= 0) return idx;
    }
    return -1;
  };

  const res = [];
  if (mode === 'buyer') {
    const cD=col('日付','Date'), cN=col('氏名','名前','Name'), cC=col('会社','Company'),
          cT=col('連絡','電話','Tel','Mail'), cA=col('エリア','地域','Area','所在','場所'),
          cB=col('予算','Budget','金額'), cTy=col('種別','種類','Type'),
          cS=col('面積','坪','㎡','Size'), cNo=col('その他','備考','条件','Note','希望');
    for (let i=headerRow+1; i=0?r[cA]:''), nm=fmt(r[cN]);
      if(!co && !ar && !nm) continue;
      res.push({date:fmt(r[cD]),name:nm,company:co,
        contact:fmt(cT>=0?r[cT]:''),area:ar,
        budget:fmt(cB>=0?r[cB]:''),type:fmt(cTy>=0?r[cTy]:''),
        size:fmt(cS>=0?r[cS]:''),note:fmt(cNo>=0?r[cNo]:'')});
    }
  } else {
    const cD=col('日付','Date'), cN=col('氏名','名前','Name'), cC=col('会社','Company'),
          cT=col('連絡','電話','Tel','Mail'), cR=col('態様','役割','Role'),
          cTy=col('種別','種類','Type'), cP=col('物件名','物件','Property'),
          cL=col('所在','住所','地','Location'), cPr=col('価格','売価','Price','金額'),
          cLa=col('土地','地積','Land'), cBl=col('建物','延床','Building'),
          cBu=col('築年','築','Built'), cNo=col('その他','備考','Note');
    for (let i=headerRow+1; i=0?r[cL]:''), nm=fmt(r[cN]);
      if(!co && !lo && !nm) continue;
      res.push({date:fmt(r[cD]),name:nm,company:co,
        contact:fmt(cT>=0?r[cT]:''),role:fmt(cR>=0?r[cR]:''),
        type:fmt(cTy>=0?r[cTy]:''),propName:fmt(cP>=0?r[cP]:''),
        location:lo,price:fmt(cPr>=0?r[cPr]:''),
        land:fmt(cLa>=0?r[cLa]:''),building:fmt(cBl>=0?r[cBl]:''),
        built:fmt(cBu>=0?r[cBu]:''),note:fmt(cNo>=0?r[cNo]:'')});
    }
  }
  return res;
}

function quickScore(b, s) {
  let sc=0;
  const bT=(b.type||'').toLowerCase(), sT=(s.type||'').toLowerCase();
  if(bT&&sT&&(bT===sT||sT.includes(bT)||bT.includes(sT))) sc+=40;
  const bA=(b.area||'').toLowerCase(), sL=(s.location||'').toLowerCase();
  for(const w of bA.replace(/[・、。]/g,' ').split(/\s+/).filter(w=>w.length>1))
    if(sL.includes(w)){sc+=30;break;}
  if(sc<30&&bA.includes('大阪')&&sL.includes('大阪')) sc+=20;
  if(bA.includes('日本全国')) sc+=15;
  return sc;
}

function jsonOut(obj) {
  return ContentService.createTextOutput(JSON.stringify(obj))
    .setMimeType(ContentService.MimeType.JSON);
}
💬 LINE通知設定(Messaging API)
📋 LINE Messaging API セットアップ手順
① LINE Developersコンソールへdevelopers.line.biz にログイン
② プロバイダーを作成:「プロバイダーを作成」→ 名前を入力
③ チャンネルを作成:「チャンネル作成」→「Messaging API」→ 必要事項入力
④ チャンネルアクセストークン発行:作成したチャンネル →「Messaging API設定」→「チャンネルアクセストークン(長期)」→「発行」
⑤ ボットをグループ招待:「Messaging API設定」の QRコードで通知先グループへ招待
⑥ グループIDを自動取得:「Messaging API設定」→「ウェブフックURL」に GAS WEB APP URL を設定 → グループ内で誰かがメッセージ送信 → GASがグループIDを自動保存
⑦ スクリプトプロパティに設定:Apps Script →「プロジェクトの設定」→「スクリプトプロパティ」に追加:
LINE_CHANNEL_ACCESS_TOKEN = (④で発行したトークン)
LINE_RECIPIENT_ID = (⑥で自動取得されたグループID)
📧 EmailJS 設定 ← emailjs.com で無料登録してIDを取得
PUBLIC KEY
SERVICE ID
TEMPLATE ID
👤 通知先担当者リスト(メール)
📋 通知ルール
📬 通知送信履歴 0件
通知履歴はありません