◇GAC #9: クレジットカード使用管理アプリの作成 8

2026年10月7日水曜日

Google Apps Script

t f B! P L

以下の調整を行う。

  1. カレンダーの終了時刻を開始時刻と同じ値にする
  2. カード種別と金額の間に":"を付加する

Code.gs

// ==========================================
// 設定項目
// ==========================================
const CALENDAR_NAME_UNPROCESSED = '未処理';
const CALENDAR_NAME_PROCESSED   = '処理済';
const STATUS_UNPROCESSED = '未処理';
const STATUS_PROCESSED   = '処理済';

const GOOGLE_MAPS_API_KEY = 'YOUR_GOOGLE_MAPS_API_KEY'; // ご自身のAPIキー

function doGet() {
  const template = HtmlService.createTemplateFromFile('index');
  template.apiKey = GOOGLE_MAPS_API_KEY;
  return template.evaluate()
    .setTitle('カード明細管理')
    .addMetaTag('viewport', 'width=device-width, initial-scale=1');
}

function getCalendarByName(name) {
  const calendars = CalendarApp.getCalendarsByName(name);
  if (calendars.length === 0) {
    throw new Error(`カレンダー「${name}」が見つかりませんでした。`);
  }
  return calendars[0];
}

/**
 * 指定日の「未処理」イベント一覧を取得する関数
 */
function getUnprocessedEventsByDate(dateStr) {
  const calUnprocessed = getCalendarByName(CALENDAR_NAME_UNPROCESSED);
  const targetDate = new Date(dateStr);
  const startOfDay = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate(), 0, 0, 0);
  const endOfDay   = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate(), 23, 59, 59);
  
  const events = calUnprocessed.getEvents(startOfDay, endOfDay);
  
  return events.map(event => {
    return {
      id: event.getId(),
      title: event.getTitle(),
      location: event.getLocation(),
      description: event.getDescription(),
      time: Utilities.formatDate(event.getStartTime(), Session.getScriptTimeZone(), "HH:mm")
    };
  });
}

/**
 * イベントIDを指定して「未処理」から「処理済」へ移動する関数
 */
function processEventById(eventId, paymentDate) {
  const calUnprocessed = getCalendarByName(CALENDAR_NAME_UNPROCESSED);
  const calProcessed   = getCalendarByName(CALENDAR_NAME_PROCESSED);
  
  const event = calUnprocessed.getEventById(eventId);
  if (!event) {
    throw new Error("対象の未処理イベントが見つかりませんでした。");
  }
  
  const title = event.getTitle();
  const startTime = event.getStartTime();
  const endTime   = new Date(startTime.getTime()); // 開始時刻と同じ値に設定
  const loc = event.getLocation();
  let desc = event.getDescription();
  
  // ステータス更新 & 支払い日の追記
  desc = desc.replace(`【ステータス】: ${STATUS_UNPROCESSED}`, `【ステータス】: ${STATUS_PROCESSED}`);
  if (paymentDate) {
    desc += `\n【カード支払い日】: ${paymentDate}`;
  }
  
  // 処理済カレンダーに新規作成
  const newProcessedEvent = calProcessed.createEvent(title, startTime, endTime, {
    location: loc,
    description: desc
  });
  
  // 未処理側を削除
  event.deleteEvent();
  
  return { success: true, title: title };
}

/**
 * 店舗の使用頻度履歴を更新
 */
function updateStoreHistory(cardCode, storeName) {
  if (!cardCode || !storeName) return;
  const userProps = PropertiesService.getUserProperties();
  const key = `HISTORY_${cardCode.toUpperCase()}`;
  let history = JSON.parse(userProps.getProperty(key) || '{}');
  history[storeName] = (history[storeName] || 0) + 1;
  userProps.setProperty(key, JSON.stringify(history));
}

/**
 * カード種別ごとの店舗利用履歴を取得
 */
function getStoreHistoryByCard(cardCode) {
  if (!cardCode) return [];
  const userProps = PropertiesService.getUserProperties();
  const key = `HISTORY_${cardCode.toUpperCase()}`;
  const history = JSON.parse(userProps.getProperty(key) || '{}');
  const sortedStores = Object.keys(history).sort((a, b) => history[b] - history[a]);
  return sortedStores.slice(0, 5);
}

/**
 * フォームからの登録処理
 */
function submitCardUsage(formData) {
  const isProcessed = formData.mode === 'processed';
  const targetCalName = isProcessed ? CALENDAR_NAME_PROCESSED : CALENDAR_NAME_UNPROCESSED;
  const statusStr     = isProcessed ? STATUS_PROCESSED : STATUS_UNPROCESSED;
  
  const cal = getCalendarByName(targetCalName);
  
  const cardCode    = formData.cardCode.toUpperCase();
  const amount      = formData.amount;
  const dateStr     = formData.date;
  const timeStr     = formData.time || '00:00';
  const location    = formData.location || '';
  const note        = formData.note || '';
  const paymentDate = formData.paymentDate || '';
  
  const title = `${cardCode}:${amount}`;
  const startTime = new Date(`${dateStr}T${timeStr}:00`);
  const endTime   = new Date(startTime.getTime()); // 開始時刻と同じ値に設定
  
  let description = `【ステータス】: ${statusStr}\n【メモ】: ${note}`;
  if (isProcessed && paymentDate) {
    description += `\n【カード支払い日】: ${paymentDate}`;
  }
  
  const event = cal.createEvent(title, startTime, endTime, {
    location: location,
    description: description
  });
  
  if (location) {
    updateStoreHistory(cardCode, location);
  }
  
  return {
    success: true,
    title: title,
    date: dateStr,
    time: timeStr,
    mode: formData.mode
  };
}

完成イメージ

かなりいい感じになった。
まずはこれで完成とする。
実際に運用を開始したが、いままでカレンダー画面でチマチマ入力したのに比べ、かなり作業効率が良くなった。

未処理登録画面

処理済登録画面


公開:2026.10.07(水) 05:40

QooQ