同じ注文を2回処理する前に止める|GASで注文行の重複をdry-run検査

同じ注文を2回処理する前に止める|GASで注文行の重複をdry-run検査

Gasおじ|GoogleアカウントをAIエージェント化

Gasおじ|GoogleアカウントをAIエージェント化

注文一覧へCSVを追記したあと、「この行は前にも取り込んだかもしれない」と不安になったことはありませんか。重複に気づかず処理を進めると、入金確認や発送準備を二重に数える原因になります。

この記事では、Googleスプレッドシートの注文表を変更せずに読み取り、注文ID・商品コード・数量・注文日の組み合わせが同じ行を重複候補として別シートへ記録する無料のGAS例を紹介します。行の削除、ステータス変更、メール送信は行いません。最初は必ずコピーしたシートと少量のサンプルで確認してください。

先に知っておきたいこと

  • 対象は自分で管理している注文一覧です。
  • 同じ注文IDでも商品コードが違う行は別の明細として扱います。
  • 一致した行は削除せず、DUPLICATE_CANDIDATEとして確認対象にします。
  • ログには購入者名、住所、メール、電話番号、注文内容を残しません。
  • 注文IDなどから作った短い照合キーだけをAuditLogへ記録します。
  • 支払い、在庫、発送、返金の判断は自動化しません。

準備するシート

スプレッドシートにOrdersという名前のシートを作り、1行目へ次の列を用意します。

  • 注文ID
  • 商品コード
  • 数量
  • 注文日
  • ステータス
  • メモ

確認用コピーには、同じ注文ID・商品コード・数量・注文日を持つ行を2行入れておくと、重複候補の動きを確かめられます。実際の購入者情報は使わず、TEST-001などの架空データで試してください。

コピーして使える最小コード

function auditDuplicateOrderRowsFree() {
  const DRY_RUN = true;
  const SOURCE_SHEET = 'Orders';
  const LOG_SHEET = 'AuditLog';
  const MAX_ROWS = 500;

  if (!DRY_RUN) {
    throw new Error('この無料例はDRY_RUN: trueのまま実行してください。');
  }

  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const source = spreadsheet.getSheetByName(SOURCE_SHEET);
  if (!source) {
    throw new Error('Ordersシートが見つかりません。');
  }

  const values = source.getDataRange().getDisplayValues();
  if (values.length < 2) {
    throw new Error('見出し行の下に確認用データを1行以上入れてください。');
  }
  if (values.length - 1 > MAX_ROWS) {
    throw new Error('最初は500行以内に絞って確認してください。');
  }

  const headers = values[0].map(value => value.trim());
  const required = ['注文ID', '商品コード', '数量', '注文日'];
  const indexes = {};
  required.forEach(name => {
    const index = headers.indexOf(name);
    if (index < 0) throw new Error('1行目に「' + name + '」が必要です。');
    indexes[name] = index;
  });

  const runId = Utilities.getUuid().slice(0, 8);
  const seen = new Map();
  const logs = [];

  values.slice(1).forEach((row, offset) => {
    const sourceRow = offset + 2;
    const orderId = normalizeOrderValueFree_(row[indexes['注文ID']]);
    const itemCode = normalizeOrderValueFree_(row[indexes['商品コード']]);
    const quantityText = normalizeOrderValueFree_(row[indexes['数量']]);
    const orderDate = normalizeOrderValueFree_(row[indexes['注文日']]);

    if (![orderId, itemCode, quantityText, orderDate].some(Boolean)) return;

    const quantity = Number(quantityText);
    if (!orderId || !itemCode || !quantityText || !orderDate) {
      logs.push([new Date(), runId, sourceRow, 'INPUT_HOLD', '', '必須項目に空欄があります']);
      return;
    }
    if (!Number.isFinite(quantity) || quantity <= 0 || !Number.isInteger(quantity)) {
      logs.push([new Date(), runId, sourceRow, 'INPUT_HOLD', '', '数量は1以上の整数で入力してください']);
      return;
    }

    const comparisonKey = [orderId, itemCode, String(quantity), orderDate].join('|');
    const shortKey = shortOrderHashFree_(comparisonKey);
    if (seen.has(comparisonKey)) {
      logs.push([
        new Date(), runId, sourceRow, 'DUPLICATE_CANDIDATE', shortKey,
        '同じ組み合わせの先行行: ' + seen.get(comparisonKey)
      ]);
    } else {
      seen.set(comparisonKey, sourceRow);
      logs.push([new Date(), runId, sourceRow, 'OK', shortKey, '一致する先行行なし']);
    }
  });

  const logSheet = spreadsheet.getSheetByName(LOG_SHEET) || spreadsheet.insertSheet(LOG_SHEET);
  if (logSheet.getLastRow() === 0) {
    logSheet.appendRow(['実行日時', 'run_id', '元の行', '判定', '照合キー', '確認メモ']);
  }
  if (logs.length > 0) {
    logSheet.getRange(logSheet.getLastRow() + 1, 1, logs.length, logs[0].length).setValues(logs);
  }

  console.log(JSON.stringify({
    dryRun: DRY_RUN,
    checkedRows: values.length - 1,
    logRows: logs.length,
    sourceUpdated: false,
    autoDeleted: false
  }));
}

function normalizeOrderValueFree_(value) {
  return String(value || '').normalize('NFKC').trim().toLowerCase();
}

function shortOrderHashFree_(value) {
  const digest = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, value);
  return Utilities.base64EncodeWebSafe(digest).slice(0, 12);
}

実行手順

1. 元の注文表をコピーし、コピー側の拡張機能からApps Scriptを開きます。

2. コードを貼り付け、auditDuplicateOrderRowsFreeを選びます。

3. DRY_RUN: trueのまま実行し、初回だけ必要な権限を内容確認して許可します。

4. AuditLogでINPUT_HOLDを先に直し、DUPLICATE_CANDIDATEの元の行と先行行を見比べます。

5. 同じ注文の正当な複数明細ではないことを確認してから、人が残す行を判断します。

6. 実行ログでsourceUpdatedがfalse、autoDeletedがfalseになっていることを確認します。

コードが行っている安全策

  • Ordersシートは読み取りだけにし、行削除やステータス変更をしません。
  • 500行を超えたら停止し、最初から大量処理しません。
  • 必須項目の空欄や不正な数量はINPUT_HOLDにします。
  • 表記ゆれを抑えるため、全角・半角と大文字・小文字をそろえて比較します。
  • 購入者情報や注文内容はAuditLogへ複製しません。
  • 重複候補でも自動削除せず、元の行番号だけを確認材料にします。

失敗しやすい点

同じ注文IDと商品コードが複数行に分かれる運用では、同じ組み合わせが正しい場合もあります。また、注文日を後から修正した行や、セット商品を分割した行は単純一致だけでは判断できません。そのため、この例は「重複確定」ではなく「重複候補」として出します。

Google Apps Scriptには実行時間やサービス利用量の上限があります。大量の注文を一度に処理せず、コピーしたシートと架空データで少量ずつ確認してください。

公開前チェックリスト

  • 元の注文行を自動削除しない
  • 入金・発送・返金ステータスを自動変更しない
  • 購入者名、住所、メール、電話番号をログへ残さない
  • INPUT_HOLDを解消してから重複候補を見る
  • 同じ商品の正当な複数明細ではないか人が確認する
  • 実行ログでsourceUpdatedとautoDeletedがfalseになっている

まとめ

注文表の重複対策は、いきなり消すことではなく、入力不備と一致候補を安全に分けて見えるようにすることから始められます。この例では注文行の組み合わせを照合し、元データを変更せず、確認用ログだけを残します。

入金・発送・売上の状態まで一つの注文台帳で整理したい方向けに、関連する有料Tipsがあります。

個人販売の注文・発送管理GAS Proを見る

Gasおじの他の商品もTips内で確認できます。

GasおじのTips商品一覧を見る

※この記事は学習・検証用の例です。実データへ適用する前にコピー環境で確認してください。Google公式・Google公認の商品ではありません。各サービスの利用条件や個人情報の取り扱いはご自身で確認してください。売上、誤発送防止、業務成果を保証するものではありません。


あなたも記事の投稿・販売を
始めてみませんか?

Tipsなら簡単に記事を販売できます!
登録無料で始められます!

Tipsなら、無料ですぐに記事の販売をはじめることができます Tipsの詳細はこちら
 

この記事のライター

Gasおじ|GoogleアカウントをAIエージェント化

おう、Gasおじだ。 AIツールとSaaSに散々突っ込んで遠回りした結果、最後に強かったのは「今あるGoogleアカウント」だった。 今ではAIエージェントであらゆるタスクを自動化して無双してるぞ。 GAS、スプレッドシート、フォーム、Gmail、AIエージェントを組み合わせれば、個人でも小さな会社でも、手作業だらけの業務はかなり自動化できる。 ここでは机上のAI論じゃなく、現場で動くテンプレ、コード、手順だけを置いていくぞ。 高いツールを増やす前に、まずお前さんのGoogleアカウントを仕事するエージェントに変えようじゃないか。

このライターが書いた他の記事

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    1 %獲得
    (29 円相当)
  • GASのtry-catch入門|元データを守るエラー処理とログの実装例

  • Gasおじ式 X投稿キュー自動実行GAS Pro|危ない時だけ止まる

    ¥2,980
    1 %獲得
    (29 円相当)

関連のおすすめ記事

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    1 %獲得
    (29 円相当)
    Gasおじ|GoogleアカウントをAIエージェント化

    Gasおじ|GoogleアカウントをAIエージェント化

  • USBメモリで持ち運べるLinux Mint 22.1と永続的な書き込み領域の作成法【Rufus だけで作成する方法を初心者向けに徹底解説】

    ¥500
    1 %獲得
    (5 円相当)
    SmartSenior

    SmartSenior

  • 【Python】 Con-Que 【データベース接続】

    ¥3,000
    1 %獲得
    (30 円相当)
    GoGorilla

    GoGorilla