0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claudeを使い、ネクストエンジンとGoogleスプレッドシートをAPI接続する:在庫情報取得試験編

Last updated at Posted at 2025-09-17

経緯

こちらのスクリプトは前回動作させてから時間が空いた場合にアクセストークンの有効期限が切れ動作しないというバグが含まれていました。
Claudeを使い、ネクストエンジンとGoogleスプレッドシートをAPI接続する:認証編のgenerateAuthUrl()を実行し、ログに出力されるURLをたどることでアクセストークンの再取得が出来るので、そうした上でスクリプトを実行しています。
早期のバグの解消をお約束いたします。

Claudeを使い、ネクストエンジンとGoogleスプレッドシートをAPI接続する:認証編でGoogle Apps Script(=GAS)とネクストエンジンの認証を終えることが出来ました。
次は在庫情報を取得する試験に移ります。
出来る方からするとそんなこと?と思われるかもしれませんが、ネットを検索しても中々こういう情報にたどり着かなかったので記事にした次第です。
試験なので数百というのではなく、GASの1回の制限時間内で取り込めるだけの在庫情報を取り込みたいと思います。
コーディングには前回と同じくClaudeを使用してまいります。
今まで、Gemini 2.5 Flash、Gemini 2.5 Pro、GPT-5、GitHub Copilotを使用してきましたが、私はClaudeでしか開発を行えませんでした。 Claudeの優れている点はいくつかあると思いますが、私はそのデバッグ能力と問題を推察して、ログを残すようにして、そのログから問題を見つける事が他のAIに足りていなくて、Claudeがすごい理由ではなかろうかと思います。

在庫情報取得スクリプトの追加

前回、NE_認証というプロジェクトを使ってステップ・バイ・ステップで進めていきましたので、同じプロジェクトを使って同じように進めていきたいと思います。

5.jpg

前回、認証.gsというスクリプトを作成しまして、必要なアクセストークン等がスクリプトプロパティに保存されていますので、同じプロジェクトに在庫情報を取得するスクリプトを作成します。
右上にある”+”をクリックするとスクリプトとHTMLが出てきますので、スクリプトをクリックします。

6.jpg

新しいスクリプトが作成されましたので、在庫情報取得試験と名前をつけます。

在庫情報取得試験.gs
/*
=============================================================================
ネクストエンジン在庫情報取得スクリプト(完全版)
=============================================================================
* 【目的】
* スプレッドシートの商品コードに対応する詳細在庫情報をネクストエンジンAPIから取得し更新
*
* 【機能】
* 1. スプレッドシートから商品コードを読み取り
* 2. ネクストエンジン商品マスタAPIで基本情報を取得
* 3. ネクストエンジン在庫マスタAPIで詳細在庫情報を取得
* 4. スプレッドシートの在庫情報を詳細データで更新
*
* 【事前設定】
* スクリプトプロパティに以下の値を設定してください:
* - SPREADSHEET_ID: 対象スプレッドシートのID
* - SHEET_NAME: 対象シート名

【スクリプトプロパティの設定方法】
1. GASエディタで「プロジェクトの設定」を開く(歯車のアイコン)
2. 「スクリプトプロパティ」セクションまでスクロール
3. 「スクリプトプロパティの追加」をクリックし、以下のキーと値を設定

   キー                     | 値
   -------------------------|------------------------------------
   SPREADSHEET_ID           | 対象スプレッドシートのID
   SHEET_NAME               | 対象シート名


* 【注意事項】
* - 認証スクリプトで事前にトークンを取得済みである必要があります
* - API制限を考慮して適切な間隔でリクエストを送信します
* - 大量データの場合は時間がかかる可能性があります

使用方法
showUsageGuide関数を実行してユーザーガイドを確認してください。

主要関数の目的
updateInventoryData()
このスクリプトのメイン機能です。
スプレッドシートに記載されているすべての商品コードについて、
ネクストエンジンAPIから最新の在庫情報を取得し、
スプレッドシートの該当する行を更新します。

getInventoryByGoodsCode(goodsCode, tokens)
特定の商品コードの完全な在庫情報を取得するための中心的な関数です。
この関数内で、以下の2つのAPIを順次呼び出しています。
1.searchGoodsWithStock(goodsCode, tokens):商品マスタAPIを呼び出して、
  商品名や基本的な在庫数など商品情報を取得します。
2.getStockByGoodsId(goodsId, tokens):在庫マスタAPIを呼び出して、
  引当数、フリー在庫数、不良在庫数など、より詳細な在庫情報を取得します。

searchGoodsWithStock(goodsCode, tokens)
ネクストエンジンの商品マスタAPIを呼び出し、
指定された商品コードの基本情報(商品ID、商品名、基本在庫数など)を取得します。

getStockByGoodsId(goodsId, tokens)
ネクストエンジンの在庫マスタAPIを呼び出し、
指定された商品IDの詳細な在庫情報を取得します。
これにより、在庫数の内訳(引当数、フリー在庫数など)がわかります。

updateRowWithInventoryData(sheet, rowIndex, inventoryData)
APIから取得した在庫データを使って、
スプレッドシートの指定された行(rowIndex)の該当する列(C列からK列)の在庫情報を更新します。

補助・テスト関数の目的
getSpreadsheetConfig()
スクリプトプロパティからスプレッドシートIDとシート名を取得します。
設定が不足している場合はエラーを発生させて処理を停止させます。

getStoredTokens()
スクリプトプロパティに保存されているアクセストークンとリフレッシュトークンを取得します。

updateStoredTokens(accessToken, refreshToken)
API呼び出し後にトークンが更新された場合、新しいトークンをスクリプトプロパティに保存します。

updateSingleProduct(goodsCode)
特定の1つの商品コードのみを対象に在庫更新処理を実行します。
主にデバッグや動作確認のために使用されます。

testStockMasterFields(goodsCode)
在庫マスタAPIが正常に動作し、
期待するフィールド(列)が返されるかを確認するためのデバッグ用関数です。

resetAllInventoryData()
スプレッドシートの在庫情報をすべて「0」にリセットします。テスト目的で使用されます。

checkScriptConfiguration()
スクリプトプロパティに、必要な設定項目(ID、トークンなど)がすべて設定されているかを確認します。

showUsageGuide()
スクリプトの使用方法や各関数の役割、注意事項などをコンソールログに表示するガイド関数です。

=============================================================================
*/

// ネクストエンジンAPIのエンドポイントは認証.gsで定義済み

// 列のマッピング0ベース
const COLUMNS = {
  GOODS_CODE: 0,              // A列: 商品コード
  GOODS_NAME: 1,              // B列: 商品名
  STOCK_QTY: 2,               // C列: 在庫数
  ALLOCATED_QTY: 3,           // D列: 引当数
  FREE_QTY: 4,                // E列: フリー在庫数
  RESERVE_QTY: 5,             // F列: 予約在庫数
  RESERVE_ALLOCATED_QTY: 6,   // G列: 予約引当数
  RESERVE_FREE_QTY: 7,        // H列: 予約フリー在庫数
  DEFECTIVE_QTY: 8,           // I列: 不良在庫数
  ORDER_REMAINING_QTY: 9,     // J列: 発注残数
  SHORTAGE_QTY: 10,           // K列: 欠品数
  JAN_CODE: 11                // L列: JANコード
};

/**
 * スプレッドシート設定を取得
 */
function getSpreadsheetConfig() {
  const properties = PropertiesService.getScriptProperties();
  const spreadsheetId = properties.getProperty('SPREADSHEET_ID');
  const sheetName = properties.getProperty('SHEET_NAME');
  
  if (!spreadsheetId || !sheetName) {
    throw new Error('スプレッドシート設定が不完全です。スクリプトプロパティにSPREADSHEET_IDとSHEET_NAMEを設定してください。');
  }
  
  return {
    spreadsheetId,
    sheetName
  };
}

/**
 * メイン関数在庫情報を更新
 */
function updateInventoryData() {
  try {
    console.log('=== 在庫情報更新開始 ===');
    
    // スプレッドシート設定を取得
    const config = getSpreadsheetConfig();
    console.log(`対象スプレッドシート: ${config.spreadsheetId}`);
    console.log(`対象シート: ${config.sheetName}`);
    
    // スプレッドシートを取得
    const spreadsheet = SpreadsheetApp.openById(config.spreadsheetId);
    const sheet = spreadsheet.getSheetByName(config.sheetName);
    
    if (!sheet) {
      throw new Error(`シート "${config.sheetName}" が見つかりません`);
    }
    
    // データ範囲を取得ヘッダー行を除く
    const lastRow = sheet.getLastRow();
    if (lastRow <= 1) {
      console.log('データが存在しません');
      return;
    }
    
    const dataRange = sheet.getRange(2, 1, lastRow - 1, 12); // 2行目から最終行までA列からL列まで
    const values = dataRange.getValues();
    
    console.log(`処理対象: ${values.length}行`);
    
    // トークンを取得
    const tokens = getStoredTokens();
    
    // 各行の在庫情報を更新
    let updateCount = 0;
    let errorCount = 0;
    
    for (let i = 0; i < values.length; i++) {
      const row = values[i];
      const goodsCode = row[COLUMNS.GOODS_CODE];
      
      if (!goodsCode) {
        console.log(`${i + 2}行目: 商品コードが空のためスキップ`);
        continue;
      }
      
      try {
        console.log(`${i + 2}行目: ${goodsCode} の在庫情報を取得中...`);
        
        // 在庫情報を取得
        const inventoryData = getInventoryByGoodsCode(goodsCode, tokens);
        
        if (inventoryData) {
          // スプレッドシートの行を更新
          updateRowWithInventoryData(sheet, i + 2, inventoryData);
          updateCount++;
          console.log(`${i + 2}行目: ${goodsCode} 更新完了`);
        } else {
          console.log(`${i + 2}行目: ${goodsCode} の在庫情報が見つかりません`);
        }
        
        // API制限を考慮して少し待機0.5- 2つのAPIを呼び出すため少し長めに設定
        Utilities.sleep(500);
        
      } catch (error) {
        console.error(`${i + 2}行目: ${goodsCode} のエラー:`, error.message);
        errorCount++;
      }
    }
    
    console.log('=== 在庫情報更新完了 ===');
    console.log(`更新成功: ${updateCount}件`);
    console.log(`エラー: ${errorCount}件`);
    
  } catch (error) {
    console.error('在庫情報更新エラー:', error.message);
    throw error;
  }
}

/**
 * 保存されたトークンを取得
 */
function getStoredTokens() {
  const properties = PropertiesService.getScriptProperties();
  const accessToken = properties.getProperty('ACCESS_TOKEN');
  const refreshToken = properties.getProperty('REFRESH_TOKEN');
  
  if (!accessToken || !refreshToken) {
    throw new Error('アクセストークンが見つかりません。先に認証を完了してください。');
  }
  
  return {
    accessToken,
    refreshToken
  };
}

/**
 * 商品コードから完全な在庫情報を取得(完全版)
 * @param {string} goodsCode - 商品コード
 * @param {Object} tokens - アクセストークンとリフレッシュトークン
 * @returns {Object|null} 完全な在庫情報
 */
function getInventoryByGoodsCode(goodsCode, tokens) {
  try {
    console.log('在庫情報取得開始...');
    console.log(`対象商品コード: ${goodsCode}`);
    
    // ステップ1: 商品マスタAPIで基本情報を取得
    console.log('商品マスタAPI呼び出し中...');
    const goodsData = searchGoodsWithStock(goodsCode, tokens);
    
    if (!goodsData) {
      console.log('商品が見つかりませんでした');
      return null;
    }
    
    console.log('商品基本情報取得完了');
    console.log(`商品名: ${goodsData.goods_name}`);
    console.log(`基本在庫数: ${goodsData.stock_quantity}`);
    
    // ステップ2: 在庫マスタAPIで詳細在庫情報を取得
    console.log('在庫マスタAPI呼び出し中...');
    const stockDetails = getStockByGoodsId(goodsCode, tokens);
    
    let completeInventoryData;
    
    if (stockDetails) {
      console.log('詳細在庫情報取得完了');
      
      // 商品情報と詳細在庫情報を結合
      completeInventoryData = {
        goods_id: goodsData.goods_id,
        goods_name: goodsData.goods_name,
        stock_quantity: parseInt(stockDetails.stock_quantity) || parseInt(goodsData.stock_quantity) || 0,
        stock_allocated_quantity: parseInt(stockDetails.stock_allocation_quantity) || 0,
        stock_free_quantity: parseInt(stockDetails.stock_free_quantity) || 0,
        stock_defective_quantity: parseInt(stockDetails.stock_defective_quantity) || 0,
        stock_advance_order_quantity: parseInt(stockDetails.stock_advance_order_quantity) || 0,
        stock_advance_order_allocation_quantity: parseInt(stockDetails.stock_advance_order_allocation_quantity) || 0,
        stock_advance_order_free_quantity: parseInt(stockDetails.stock_advance_order_free_quantity) || 0,
        stock_remaining_order_quantity: parseInt(stockDetails.stock_remaining_order_quantity) || 0,
        stock_out_quantity: parseInt(stockDetails.stock_out_quantity) || 0
      };
      
      console.log('完全な在庫情報を構築しました:');
      console.log(`- 在庫数: ${completeInventoryData.stock_quantity}`);
      console.log(`- 引当数: ${completeInventoryData.stock_allocated_quantity}`);
      console.log(`- フリー在庫数: ${completeInventoryData.stock_free_quantity}`);
      console.log(`- 不良在庫数: ${completeInventoryData.stock_defective_quantity}`);
      console.log(`- 予約在庫数: ${completeInventoryData.stock_advance_order_quantity}`);
      console.log(`- 予約引当数: ${completeInventoryData.stock_advance_order_allocation_quantity}`);
      console.log(`- 予約フリー在庫数: ${completeInventoryData.stock_advance_order_free_quantity}`);
      console.log(`- 発注残数: ${completeInventoryData.stock_remaining_order_quantity}`);
      console.log(`- 欠品数: ${completeInventoryData.stock_out_quantity}`);
      
    } else {
      console.log('詳細在庫情報が取得できませんでした。基本情報のみで構築します。');
      
      // 詳細情報が取得できない場合は基本情報のみ使用
      completeInventoryData = {
        goods_id: goodsData.goods_id,
        goods_name: goodsData.goods_name,
        stock_quantity: parseInt(goodsData.stock_quantity) || 0,
        stock_allocated_quantity: 0,
        stock_free_quantity: 0,
        stock_defective_quantity: 0,
        stock_advance_order_quantity: 0,
        stock_advance_order_allocation_quantity: 0,
        stock_advance_order_free_quantity: 0,
        stock_remaining_order_quantity: 0,
        stock_out_quantity: 0
      };
    }
    
    console.log('在庫情報取得完了');
    return completeInventoryData;
    
  } catch (error) {
    console.error(`商品コード ${goodsCode} の在庫取得エラー:`, error.message);
    console.error('エラー詳細:', error.stack);
    return null;
  }
}

/**
 * 商品コードで商品マスタを検索し在庫情報も取得(修正版)
 * @param {string} goodsCode - 商品コード
 * @param {Object} tokens - トークン情報
 * @returns {Object|null} 商品情報と在庫情報
 */
function searchGoodsWithStock(goodsCode, tokens) {
  const url = `${NE_API_URL}/api_v1_master_goods/search`;
  const payload = {
    'access_token': tokens.accessToken,
    'refresh_token': tokens.refreshToken,
    'goods_id-eq': goodsCode, // goods_idで検索
    'fields': 'goods_id,goods_name,stock_quantity'
  };
  
  const options = {
    'method': 'POST',
    'headers': {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    'payload': Object.keys(payload).map(key =>
      encodeURIComponent(key) + '=' + encodeURIComponent(payload[key])
    ).join('&')
  };
  
  try {
    const response = UrlFetchApp.fetch(url, options);
    const responseText = response.getContentText();
    const responseData = JSON.parse(responseText);
    
    console.log('商品マスタAPI応答:', responseData.result);
    
    // トークンが更新された場合は保存
    if (responseData.access_token && responseData.refresh_token) {
      updateStoredTokens(responseData.access_token, responseData.refresh_token);
    }
    
    if (responseData.result === 'success') {
      if (responseData.data && responseData.data.length > 0) {
        const goodsData = responseData.data[0];
        console.log('取得した商品データ:', goodsData);
        return {
          goods_id: goodsData.goods_id,
          goods_name: goodsData.goods_name,
          stock_quantity: goodsData.stock_quantity
        };
      } else {
        console.log(`商品コード ${goodsCode} が見つかりません`);
        return null;
      }
    } else {
      console.error(`商品検索エラー:`, JSON.stringify(responseData));
      if (responseData.message) {
        console.error('エラーメッセージ:', responseData.message);
      }
      return null;
    }
  } catch (error) {
    console.error('商品マスタAPI呼び出しエラー:', error.toString());
    return null;
  }
}

/**
 * 商品IDから詳細在庫情報を取得(完全版)
 * @param {string} goodsId - 商品ID
 * @param {Object} tokens - トークン情報
 * @returns {Object|null} 詳細在庫情報
 */
function getStockByGoodsId(goodsId, tokens) {
  const url = `${NE_API_URL}/api_v1_master_stock/search`;
  const payload = {
    'access_token': tokens.accessToken,
    'refresh_token': tokens.refreshToken,
    'stock_goods_id-eq': goodsId, // 正しいフィールド名
    'fields': 'stock_goods_id,stock_quantity,stock_allocation_quantity,stock_defective_quantity,stock_remaining_order_quantity,stock_out_quantity,stock_free_quantity,stock_advance_order_quantity,stock_advance_order_allocation_quantity,stock_advance_order_free_quantity'
  };
  
  const options = {
    'method': 'POST',
    'headers': {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    'payload': Object.keys(payload).map(key =>
      encodeURIComponent(key) + '=' + encodeURIComponent(payload[key])
    ).join('&')
  };
  
  try {
    const response = UrlFetchApp.fetch(url, options);
    const responseText = response.getContentText();
    const responseData = JSON.parse(responseText);
    
    console.log('在庫マスタAPI応答:', responseData.result);
    
    // トークンが更新された場合は保存
    if (responseData.access_token && responseData.refresh_token) {
      updateStoredTokens(responseData.access_token, responseData.refresh_token);
    }
    
    if (responseData.result === 'success' && responseData.data && responseData.data.length > 0) {
      const stockData = responseData.data[0];
      console.log('取得した詳細在庫データ:', stockData);
      
      // 在庫マスタAPIから取得したデータをそのまま返す
      return stockData;
    } else {
      console.log(`商品ID ${goodsId} の在庫情報が見つかりません`);
      console.log('API応答詳細:', JSON.stringify(responseData, null, 2));
      return null;
    }
  } catch (error) {
    console.error('在庫マスタAPI呼び出しエラー:', error.toString());
    return null;
  }
}

/**
 * スプレッドシートの行を在庫データで更新(完全版)
 * @param {Sheet} sheet - シートオブジェクト
 * @param {number} rowIndex - 更新する行番号(1ベース)
 * @param {Object} inventoryData - 在庫データ
 */
function updateRowWithInventoryData(sheet, rowIndex, inventoryData) {
  // 在庫情報の列を更新C列からK列まで
  const updateValues = [
    inventoryData.stock_quantity || 0,                              // C列: 在庫数
    inventoryData.stock_allocated_quantity || 0,                    // D列: 引当数
    inventoryData.stock_free_quantity || 0,                         // E列: フリー在庫数
    inventoryData.stock_advance_order_quantity || 0,                // F列: 予約在庫数
    inventoryData.stock_advance_order_allocation_quantity || 0,     // G列: 予約引当数
    inventoryData.stock_advance_order_free_quantity || 0,           // H列: 予約フリー在庫数
    inventoryData.stock_defective_quantity || 0,                    // I列: 不良在庫数
    inventoryData.stock_remaining_order_quantity || 0,              // J列: 発注残数
    inventoryData.stock_out_quantity || 0                           // K列: 欠品数
  ];
  
  // C列からK列まで更新
  const range = sheet.getRange(rowIndex, COLUMNS.STOCK_QTY + 1, 1, updateValues.length);
  range.setValues([updateValues]);
  
  console.log('更新データ:', updateValues);
  console.log('在庫管理スクリプト: データ取得の不具合で最新版を実行したログとなります。');
  console.log('');
  console.log(`取得した在庫データ: { goods_id: '${inventoryData.goods_id}', goods_name: '${inventoryData.goods_name}', stock_quantity: ${inventoryData.stock_quantity}, stock_allocated_quantity: ${inventoryData.stock_allocated_quantity}, stock_free_quantity: ${inventoryData.stock_free_quantity}, stock_defective_quantity: ${inventoryData.stock_defective_quantity}, stock_advance_order_quantity: ${inventoryData.stock_advance_order_quantity}, stock_advance_order_allocation_quantity: ${inventoryData.stock_advance_order_allocation_quantity}, stock_advance_order_free_quantity: ${inventoryData.stock_advance_order_free_quantity}, stock_remaining_order_quantity: ${inventoryData.stock_remaining_order_quantity}, stock_out_quantity: ${inventoryData.stock_out_quantity} }`);
}

/**
 * トークンを更新保存
 * @param {string} accessToken - 新しいアクセストークン
 * @param {string} refreshToken - 新しいリフレッシュトークン
 */
function updateStoredTokens(accessToken, refreshToken) {
  const properties = PropertiesService.getScriptProperties();
  properties.setProperties({
    'ACCESS_TOKEN': accessToken,
    'REFRESH_TOKEN': refreshToken,
    'TOKEN_UPDATED_AT': new Date().getTime().toString()
  });
  console.log('トークンを更新しました');
}

/**
 * 特定の商品コードのみ更新(テスト用・完全版)
 * @param {string} goodsCode - 更新したい商品コード
 */
function updateSingleProduct(goodsCode) {
  try {
    console.log('=== バージョン確認: 完全版が実行されています ===');
    console.log(`=== 単品更新開始: ${goodsCode} ===`);
    
    // スプレッドシート設定を取得
    const config = getSpreadsheetConfig();
    
    const spreadsheet = SpreadsheetApp.openById(config.spreadsheetId);
    console.log('スプレッドシート取得成功');
    
    const sheet = spreadsheet.getSheetByName(config.sheetName);
    console.log('シート取得結果:', sheet ? 'success' : 'null');
    
    if (!sheet) {
      throw new Error(`シート "${config.sheetName}" が見つかりません`);
    }
    
    // 商品コードを検索
    const dataRange = sheet.getRange(2, 1, sheet.getLastRow() - 1, 12);
    console.log('データ範囲取得成功');
    
    const values = dataRange.getValues();
    console.log('データ取得成功、行数:', values.length);
    
    let targetRowIndex = -1;
    for (let i = 0; i < values.length; i++) {
      if (values[i][COLUMNS.GOODS_CODE] === goodsCode) {
        targetRowIndex = i + 2; // 実際の行番号1ベース
        break;
      }
    }
    
    if (targetRowIndex === -1) {
      throw new Error(`商品コード ${goodsCode} がスプレッドシートに見つかりません`);
    }
    
    console.log('商品コード発見、行番号:', targetRowIndex);
    
    // トークンを取得
    const tokens = getStoredTokens();
    console.log('トークン取得成功');
    
    // 在庫情報を取得
    console.log('getInventoryByGoodsCode 関数を呼び出します');
    const inventoryData = getInventoryByGoodsCode(goodsCode, tokens);
    console.log('getInventoryByGoodsCode 完了、結果:', inventoryData ? 'データあり' : 'データなし');
    console.log('在庫情報取得結果:', inventoryData ? 'success' : 'null');
    
    if (inventoryData) {
      console.log('在庫データ更新開始...');
      updateRowWithInventoryData(sheet, targetRowIndex, inventoryData);
      console.log(`商品コード ${goodsCode} の更新が完了しました`);
    } else {
      console.log(`商品コード ${goodsCode} の在庫情報が見つかりませんでした`);
    }
    
  } catch (error) {
    console.error('単品更新エラー:', error.message);
    console.error('エラー発生箇所の詳細:', error.stack);
    throw error;
  }
}

/**
 * 在庫マスタAPIのフィールド確認用テスト関数
 */
function testStockMasterFields(goodsCode = "実際の商品コード") {
  try {
    console.log('=== 在庫マスタAPI フィールド確認テスト ===');
    console.log(`対象商品コード: ${goodsCode}`);
    
    const tokens = getStoredTokens();
    const url = `${NE_API_URL}/api_v1_master_stock/search`;
    const payload = {
      'access_token': tokens.accessToken,
      'refresh_token': tokens.refreshToken,
      'stock_goods_id-eq': goodsCode,
      'fields': 'stock_goods_id,stock_quantity,stock_allocation_quantity,stock_defective_quantity,stock_remaining_order_quantity,stock_out_quantity,stock_free_quantity,stock_advance_order_quantity,stock_advance_order_allocation_quantity,stock_advance_order_free_quantity'
    };
    
    const options = {
      'method': 'POST',
      'headers': {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      'payload': Object.keys(payload).map(key =>
        encodeURIComponent(key) + '=' + encodeURIComponent(payload[key])
      ).join('&')
    };
    
    const response = UrlFetchApp.fetch(url, options);
    const responseText = response.getContentText();
    const responseData = JSON.parse(responseText);
    
    console.log('API応答結果:', responseData.result);
    
    if (responseData.result === 'success' && responseData.data && responseData.data.length > 0) {
      console.log('取得できたフィールド一覧:');
      const stockData = responseData.data[0];
      Object.keys(stockData).forEach(key => {
        console.log(`- ${key}: ${stockData[key]}`);
      });
    } else {
      console.log('データが取得できませんでした');
      console.log('応答詳細:', JSON.stringify(responseData, null, 2));
    }
  } catch (error) {
    console.error('テストエラー:', error.message);
  }
}

/**
 * 在庫情報の手動リセット(テスト用)
 * すべての在庫数値を0にリセット
 */
function resetAllInventoryData() {
  try {
    console.log('=== 在庫情報リセット開始 ===');
    
    // スプレッドシート設定を取得
    const config = getSpreadsheetConfig();
    
    const spreadsheet = SpreadsheetApp.openById(config.spreadsheetId);
    const sheet = spreadsheet.getSheetByName(config.sheetName);
    const lastRow = sheet.getLastRow();
    
    if (lastRow <= 1) {
      console.log('データが存在しません');
      return;
    }
    
    // C列からK列までを0でクリア
    const range = sheet.getRange(2, COLUMNS.STOCK_QTY + 1, lastRow - 1, 9);
    const resetValues = Array(lastRow - 1).fill(Array(9).fill(0));
    range.setValues(resetValues);
    
    console.log(`${lastRow - 1}行の在庫情報をリセットしました`);
  } catch (error) {
    console.error('リセットエラー:', error.message);
    throw error;
  }
}

/**
 * スプレッドシートのシート名を確認
 */
function checkSheetNames() {
  try {
    // スプレッドシート設定を取得
    const config = getSpreadsheetConfig();
    
    console.log('使用しているスプレッドシートID:', config.spreadsheetId);
    const spreadsheet = SpreadsheetApp.openById(config.spreadsheetId);
    const sheets = spreadsheet.getSheets();
    
    console.log('=== スプレッドシート内のシート名一覧 ===');
    for (let i = 0; i < sheets.length; i++) {
      const sheetName = sheets[i].getName();
      console.log(`シート${i + 1}: "${sheetName}" (文字数: ${sheetName.length})`);
    }
    console.log('');
    console.log('現在のSHEET_NAME設定:', `"${config.sheetName}"`);
    console.log('上記のシート名のいずれかと完全に一致するようにSHEET_NAMEを設定してください');
  } catch (error) {
    console.error('シート名確認エラー:', error.message);
    console.error('スプレッドシートIDまたは設定が正しいか確認してください');
  }
}

/**
 * スクリプト設定状況を確認
 */
function checkScriptConfiguration() {
  try {
    console.log('=== スクリプト設定状況確認 ===');
    
    const properties = PropertiesService.getScriptProperties();
    
    // 必要な設定項目をチェック
    const requiredProperties = [
      'CLIENT_ID',
      'CLIENT_SECRET', 
      'REDIRECT_URI',
      'SPREADSHEET_ID',
      'SHEET_NAME',
      'ACCESS_TOKEN',
      'REFRESH_TOKEN'
    ];
    
    console.log('【必須設定項目の確認】');
    let allConfigured = true;
    
    requiredProperties.forEach(prop => {
      const value = properties.getProperty(prop);
      const status = value ? '✓ 設定済み' : '✗ 未設定';
      console.log(`${prop}: ${status}`);
      
      if (!value) {
        allConfigured = false;
      }
    });
    
    console.log('');
    console.log(`【設定完了状況】: ${allConfigured ? '✓ 全て設定済み' : '✗ 未設定項目があります'}`);
    
    if (!allConfigured) {
      console.log('');
      console.log('【設定方法】');
      console.log('GASエディタで以下の手順で設定してください:');
      console.log('1. 左メニューの「設定」(歯車アイコン)をクリック');
      console.log('2. 「スクリプト プロパティ」セクションで「スクリプト プロパティを追加」をクリック');
      console.log('3. 以下のプロパティを追加:');
      console.log('   - SPREADSHEET_ID: 対象スプレッドシートのID');
      console.log('   - SHEET_NAME: 対象シート名');
      console.log('   ※ 認証関連は認証.gsで設定済みの場合はそのまま使用');
    }
    
  } catch (error) {
    console.error('設定確認エラー:', error.message);
  }
}

/**
 * スクリプト使用方法ガイド(修正版)
 */
function showUsageGuide() {
  console.log('=== 在庫情報取得スクリプト使用方法(修正版) ===');
  console.log('');
  console.log('【事前設定】');
  console.log('スクリプトプロパティに以下を設定してください:');
  console.log('- SPREADSHEET_ID: 対象スプレッドシートのID');
  console.log('- SHEET_NAME: 対象シート名');
  console.log('- その他認証関連設定(認証.gsで設定済みの場合はOK)');
  console.log('');
  console.log('【設定確認】');
  console.log('- checkScriptConfiguration(): 設定状況の確認');
  console.log('');
  console.log('【前提条件】');
  console.log('- 認証スクリプトでトークンが取得済みであること');
  console.log('- スプレッドシートに商品コードが入力済みであること');
  console.log('');
  console.log('【主要関数】');
  console.log('1. updateInventoryData()');
  console.log('   - 全商品の詳細在庫情報を更新');
  console.log('   - 処理時間: 商品数 × 約2.5秒(2つのAPIを呼び出すため)');
  console.log('');
  console.log('2. updateSingleProduct("商品コード")');
  console.log('   - 特定商品の詳細在庫情報のみ更新(テスト用)');
  console.log('   - 例: updateSingleProduct("実際の商品コード")');
  console.log('');
  console.log('3. testStockMasterFields("商品コード")');
  console.log('   - 在庫マスタAPIの動作確認(デバッグ用)');
  console.log('');
  console.log('4. resetAllInventoryData()');
  console.log('   - 全在庫数値を0にリセット(テスト用)');
  console.log('');
  console.log('【更新される在庫情報】');
  console.log('- C列: 在庫数');
  console.log('- D列: 引当数');
  console.log('- E列: フリー在庫数');
  console.log('- F列: 予約在庫数');
  console.log('- G列: 予約引当数');
  console.log('- H列: 予約フリー在庫数');
  console.log('- I列: 不良在庫数');
  console.log('- J列: 発注残数');
  console.log('- K列: 欠品数');
  console.log('');
  console.log('【注意事項】');
  console.log('- APIレート制限のため各商品間に2秒の待機時間があります');
  console.log('- 商品マスタAPIと在庫マスタAPIの2つを順次呼び出すため処理時間が長くなります');
  console.log('- 大量の商品がある場合は処理時間が長くなります');
  console.log('- エラーが発生した商品はスキップされます');
  console.log('');
  console.log('【実行推奨順序】');
  console.log('1. まず checkScriptConfiguration() で設定確認');
  console.log('2. 次に testStockMasterFields("商品コード") でAPIの動作確認');
  console.log('3. 次に testSingleUpdate() で詳細在庫取得をテスト');
  console.log('4. 問題なければ updateInventoryData() で全更新');
}

/**
 * テスト実行用関数(設定値を使用)
 */
function testSingleUpdate() {
  // デフォルトの商品コードでテスト実際の商品コードに変更してください
  updateSingleProduct("実際の商品コード");
}

で、コードを全部ぶち込む(笑)

7.jpg

ぶち込んだらドライブにプロジェクトを保存しておきます。

8.jpg

次に、データを書きに行くスプレッドシートを作成しておきます。
スプレッドシート名は何でも良い(スクリプトプロパティに関係ない)ので、任意の名前にします。
必要なのがスプレッドシートIDとシート名です。
スプレッドシートIDはURLの一部になっていまして、黄色丸で囲った部分の
d/1~~~~~~~~~~~~~o/
のスラッシュの間になります。
1~~~~~~~~~~~~~o
これがスプレッドシートIDです。

シート名は今回はGASで書きに行くので”GAS”としました。

更に、1列目はヘッダーになりますので、A~L列に画像のようにセルに入力します。

スクリプトの冒頭で
* 【事前設定】
* スクリプトプロパティに以下の値を設定してください:
* - SPREADSHEET_ID: 対象スプレッドシートのID
* - SHEET_NAME: 対象シート名
と書かれていますので、スクリプトプロパティを書きに行きます。

9.jpg

歯車マークにカーソルを合わせてプロジェクトの設定に進みます。

10.jpg

”スクリプトプロパティを編集”をクリックします。

11.jpg

”スクリプトプロパティを追加”を2回クリックします。

12.jpg

2項目の追加ができるようになりました。

13.jpg

1つはプロパティ名を”SPREADSHEET_ID”として、値に先ほど確認した対象スプレッドシートのIDを入力します。
もう1つはプロパティ名を”SHEET_NAME”として、先ほど設定した対象シート名(GAS)を入力して、”スクリプトプロパティを保存”をクリックさせます。

14.jpg

showUsageGuide()関数が下の方にあるのでそちらを実行させると操作ガイドが出てきます。
checkScriptConfiguration()関数を実行すると・・・
17:23:24 お知らせ 実行開始
17:23:24 情報 === スクリプト設定状況確認 ===
17:23:24 情報 【必須設定項目の確認】
17:23:24 情報 CLIENT_ID: ✓ 設定済み
17:23:24 情報 CLIENT_SECRET: ✓ 設定済み
17:23:24 情報 REDIRECT_URI: ✓ 設定済み
17:23:24 情報 SPREADSHEET_ID: ✓ 設定済み
17:23:25 情報 SHEET_NAME: ✓ 設定済み
17:23:25 情報 ACCESS_TOKEN: ✓ 設定済み
17:23:25 情報 REFRESH_TOKEN: ✓ 設定済み
17:23:25 情報
17:23:25 情報 【設定完了状況】: ✓ 全て設定済み
17:23:25 お知らせ 実行完了
となり、必要な設定がすべて終わっていることが確認できます。

554行目にある
function testStockMasterFields(goodsCode = "テストしたい商品コード一つ") {
のテストしたい商品コード一つに実際にネクストエンジン(サンドボックス環境)登録されている商品コードを入力して
testStockMasterFields(goodsCode = "テストしたい商品コード一つ")関数を実行させると・・・
17:24:56 お知らせ 実行開始
17:24:56 情報 === 在庫マスタAPI フィールド確認テスト ===
17:24:56 情報 対象商品コード: テストしたい商品コード一つ
17:24:57 情報 API応答結果: success
17:24:57 情報 取得できたフィールド一覧:
17:24:57 情報 - stock_goods_id: テストしたい商品コード一つ
17:24:57 情報 - stock_quantity: 722
17:24:57 情報 - stock_allocation_quantity: 4
17:24:57 情報 - stock_defective_quantity: 0
17:24:57 情報 - stock_remaining_order_quantity: 0
17:24:57 情報 - stock_out_quantity: 0
17:24:57 情報 - stock_free_quantity: 718
17:24:57 情報 - stock_advance_order_quantity: 0
17:24:57 情報 - stock_advance_order_allocation_quantity: 0
17:24:57 情報 - stock_advance_order_free_quantity: 0
17:24:57 お知らせ 実行完了
となり、APIの動作が確認できます。

773行目の
updateSingleProduct("テストしたい商品コード一つ");
に実際にスプレッドシートとネクストエンジン(サンドボックス環境)に登録されている商品コードを登録して、
testSingleUpdate()関数を実行させると
17:32:05 お知らせ 実行開始
17:32:05 情報 === バージョン確認: 完全版が実行されています ===
17:32:05 情報 === 単品更新開始: テストしたい商品コード一つ ===
17:32:05 情報 スプレッドシート取得成功
17:32:05 情報 シート取得結果: success
17:32:05 情報 データ範囲取得成功
17:32:05 情報 データ取得成功、行数: 1
17:32:05 情報 商品コード発見、行番号: 2
17:32:05 情報 トークン取得成功
17:32:05 情報 getInventoryByGoodsCode 関数を呼び出します
17:32:05 情報 在庫情報取得開始...
17:32:05 情報 対象商品コード: テストしたい商品コード一つ
17:32:05 情報 商品マスタAPI呼び出し中...
17:32:06 情報 商品マスタAPI応答: success
17:32:06 情報 トークンを更新しました
17:32:06 情報 取得した商品データ: { goods_id: 'テストしたい商品コード一つ',
goods_name: '*****',
stock_quantity: '20' }
17:32:06 情報 商品基本情報取得完了
17:32:06 情報 商品名: *****
17:32:06 情報 基本在庫数: 20
17:32:06 情報 在庫マスタAPI呼び出し中...
17:32:07 情報 在庫マスタAPI応答: success
17:32:07 情報 トークンを更新しました
17:32:07 情報 取得した詳細在庫データ: { stock_goods_id: 'テストしたい商品コード一つ',
stock_quantity: '20',
stock_allocation_quantity: '0',
stock_defective_quantity: '0',
stock_remaining_order_quantity: '0',
stock_out_quantity: '0',
stock_free_quantity: '20',
stock_advance_order_quantity: '0',
stock_advance_order_allocation_quantity: '0',
stock_advance_order_free_quantity: '0' }
17:32:07 情報 詳細在庫情報取得完了
17:32:07 情報 完全な在庫情報を構築しました:
17:32:07 情報 - 在庫数: 20
17:32:07 情報 - 引当数: 0
17:32:07 情報 - フリー在庫数: 20
17:32:07 情報 - 不良在庫数: 0
17:32:07 情報 - 予約在庫数: 0
17:32:07 情報 - 予約引当数: 0
17:32:07 情報 - 予約フリー在庫数: 0
17:32:07 情報 - 発注残数: 0
17:32:07 情報 - 欠品数: 0
17:32:07 情報 在庫情報取得完了
17:32:07 情報 getInventoryByGoodsCode 完了、結果: データあり
17:32:07 情報 在庫情報取得結果: success
17:32:07 情報 在庫データ更新開始...
17:32:07 情報 更新データ: [ 20, 0, 20, 0, 0, 0, 0, 0, 0 ]
17:32:07 情報 在庫管理スクリプト: データ取得の不具合で最新版を実行したログとなります。
17:32:07 情報
17:32:07 情報 取得した在庫データ: { goods_id: 'テストしたい商品コード一つ', goods_name: '*****', stock_quantity: 20, stock_allocated_quantity: 0, stock_free_quantity: 20, stock_defective_quantity: 0, stock_advance_order_quantity: 0, stock_advance_order_allocation_quantity: 0, stock_advance_order_free_quantity: 0, stock_remaining_order_quantity: 0, stock_out_quantity: 0 }
17:32:07 情報 商品コード テストしたい商品コード一つ の更新が完了しました
17:32:07 お知らせ 実行完了
となりまして、ネクストエンジンに登録されている在庫情報が、スプレッドシートに登録されている対象商品の在庫情報として書き込まれていることが確認できます。

ここまで来たらネクストエンジン(サンドボックス環境)に登録されている商品コードをいくつもスプレッドシートのA列に登録してください。
登録したらいよいよupdateInventoryData()関数を実行させてみましょう。

こちらのようになり在庫情報が取得できたことが確認できると思います。
接続試験編ということで、こちらのスクリプトで取得できる在庫情報は、GASの1回の実行時間の上限である6分で200行程となります。
おぉ~
ログを見ていると楽しいものですね~
ネクストエンジンとGoogleスプレッドシートがAPI接続できました。
ということで今回はここまでです。

次回は200行ほどしか取得できない状況をもう少し高速化するのと、1000行程までを何回か実行させることで在庫情報を取得させるところまでの記事にしたいと思います。
引き続きよろしくお願い致します。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?